1//////////////////////////////////////////////////////////////////////
   2// LibFile: attachments.scad
   3//   The modules in this file allows you to attach one object to another by making one object the child of another object.
   4//   You can place the child object in relation to its parent object and control the position and orientation
   5//   relative to the parent.  The modifiers allow you to treat children in ways different from simple union, such
   6//   as differencing them from the parent, or changing their color.  Attachment only works when the parent and child
   7//   are both written to support attachment.  Also included in this file  are the tools to make your own "attachable" objects.
   8// Includes:
   9//   include <BOSL2/std.scad>
  10// FileGroup: Basic Modeling
  11// FileSummary: Positioning objects on or relative to other objects.  Making your own objects support attachment.
  12// FileFootnotes: STD=Included in std.scad
  13//////////////////////////////////////////////////////////////////////
  14
  15
  16// Default values for attachment code.
  17$tags=undef;      // for backward compatibility
  18$tag = "";
  19$tag_prefix = "";
  20$overlap = 0;
  21$color = "default";
  22$save_color = undef;         // Saved color to revert back for children
  23
  24$anchor_override = undef;
  25$attach_to = undef;
  26$attach_anchor = [CENTER, CENTER, UP, 0];
  27$attach_alignment = undef;
  28
  29$parent_anchor = BOTTOM;
  30$parent_spin = 0;
  31$parent_orient = UP;
  32
  33$parent_size = undef;
  34$parent_geom = undef;
  35
  36$tags_shown = "ALL";
  37$tags_hidden = [];
  38
  39_ANCHOR_TYPES = ["intersect","hull"];
  40
  41
  42// Section: Terminology and Shortcuts
  43//   This library adds the concept of anchoring, spin and orientation to the `cube()`, `cylinder()`
  44//   and `sphere()` builtins, as well as to most of the shapes provided by this library itself.
  45//   - An anchor is a place on an object which you can align the object to, or attach other objects
  46//     to using `attach()` or `position()`. An anchor has a position, a direction, and a spin.
  47//     The direction and spin are used to orient other objects to match when using `attach()`.
  48//   - Spin is a simple rotation around the Z axis.
  49//   - Orientation is rotating an object so that its top is pointed towards a given vector.
  50//   .
  51//   An object will first be translated to its anchor position, then spun, then oriented.
  52//   For a detailed step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
  53//   .
  54//   For describing directions, faces, edges, and corners the library provides a set of shortcuts
  55//   all based on combinations of unit direction vectors.  You can use these for anchoring and orienting
  56//   attachable objects.  You can also them to specify edge sets for rounding or chamfering cuboids,
  57//   or for placing edge, face and corner masks.
  58// Subsection: Anchor
  59//   Anchoring is specified with the `anchor` argument in most shape modules.  Specifying `anchor`
  60//   when creating an object will translate the object so that the anchor point is at the origin
  61//   (0,0,0).  Anchoring always occurs before spin and orientation are applied.
  62//   .
  63//   An anchor can be referred to in one of two ways; as a directional vector, or as a named anchor string.
  64//   .
  65//   When given as a vector, it points, in a general way, towards the face, edge, or corner of the
  66//   object that you want the anchor for, relative to the center of the object.  You can simply
  67//   specify a vector like `[0,0,1]` to anchor an object at the Z+ end, but you can also use
  68//   directional constants with names like `TOP`, `BOTTOM`, `LEFT`, `RIGHT` and `BACK` that you can add together
  69//   to specify anchor points.  See [specifying directions](attachments.scad#subsection-specifying-directions)
  70//   below for the full list of pre-defined directional constants.
  71//   .
  72//   For example:
  73//   - `[0,0,1]` is the same as `TOP` and refers to the center of the top face.
  74//   - `[-1,0,1]` is the same as `TOP+LEFT`, and refers to the center of the top-left edge.
  75//   - `[1,1,-1]` is the same as `BOTTOM+BACK+RIGHT`, and refers to the bottom-back-right corner.
  76//   .
  77//   When the object is cubical or rectangular in shape the anchors must have zero or one values
  78//   for their components and they refer to the face centers, edge centers, or corners of the object.
  79//   The direction of a face anchor will be perpendicular to the face, pointing outward.  The direction of a edge anchor
  80//   will be the average of the anchor directions of the two faces the edge is between.  The direction
  81//   of a corner anchor will be the average of the anchor directions of the three faces the corner is
  82//   on.
  83//   .
  84//   When the object is cylindrical, conical, or spherical in nature, the anchors will be located
  85//   around the surface of the cylinder, cone, or sphere, relative to the center.
  86//   You can generally use an arbitrary vector to get an anchor positioned anywhere on the curved
  87//   surface of such an object, and the anchor direction will be the surface normal at the anchor location.
  88//   However, for anchor component pointing toward the flat face should be either -1, 1, or 0, and
  89//   anchors that point diagonally toward one of the flat faces will select a point on the edge.
  90//   .
  91//   For objects in two dimensions, the natural expectation is for TOP and BOTTOM to refer to the Y direction
  92//   of the shape.  To support this, if you give an anchor in 2D that has anchor.y=0 then the Z component
  93//   will be mapped to the Y direction.  This  means you can use TOP and BOTTOM for anchors of 2D objects.
  94//   But remember that TOP and BOTTOM are three dimensional vectors and this is a special interpretation
  95//   for 2d anchoring.
  96//   .
  97//   Some more complex objects, like screws and stepper motors, have named anchors to refer to places
  98//   on the object that are not at one of the standard faces, edges or corners.  For example, stepper
  99//   motors have anchors for `"screw1"`, `"screw2"`, etc. to refer to the various screwholes on the
 100//   stepper motor shape.  The names, positions, directions, and spins of these anchors are
 101//   specific to the object, and are documented when they exist.
 102// Subsection: Spin
 103//   Spin is specified with the `spin` argument in most shape modules.  Specifying a scalar `spin`
 104//   when creating an object will rotate the object counter-clockwise around the Z axis by the given
 105//   number of degrees.  If given as a 3D vector, the object will be rotated around each of the X, Y, Z
 106//   axes by the number of degrees in each component of the vector.  Spin is always applied after
 107//   anchoring, and before orientation.  Since spin is applied after anchoring it is not what
 108//   you might think of intuitively as spinning the shape.  To do that, apply `zrot()` to the shape before anchoring.
 109// Subsection: Orient
 110//   Orientation is specified with the `orient` argument in most shape modules.  Specifying `orient`
 111//   when creating an object will rotate the object such that the top of the object will be pointed
 112//   at the vector direction given in the `orient` argument.  Orientation is always applied after
 113//   anchoring and spin.  The constants `UP`, `DOWN`, `FRONT`, `BACK`, `LEFT`, and `RIGHT` can be
 114//   added together to form the directional vector for this.  ie: `LEFT+BACK`
 115// Subsection: Specifying Directions
 116//   You can use direction vectors to specify anchors for objects or to specify edges, faces, and
 117//   corners of cubes.  You can simply specify these direction vectors numerically, but another
 118//   option is to use named constants for direction vectors.  These constants define unit vectors
 119//   for the six axis directions as shown below.
 120// Figure(3D,Big,VPD=6): Named constants for direction vectors.  Some directions have more than one name.
 121//   $fn=12;
 122//   stroke([[0,0,0],RIGHT], endcap2="arrow2", width=.05);
 123//   color("black")right(.05)up(.05)move(RIGHT) text3d("RIGHT",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 124//   stroke([[0,0,0],LEFT], endcap2="arrow2", width=.05);
 125//   color("black")left(.05)up(.05)move(LEFT) text3d("LEFT",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 126//   stroke([[0,0,0],FRONT], endcap2="arrow2", width=.05);
 127//   color("black")
 128//   left(.1){
 129//   up(.12)move(FRONT) text3d("FRONT",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 130//   move(FRONT) text3d("FWD",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 131//   down(.12)move(FRONT) text3d("FORWARD",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 132//   }
 133//   stroke([[0,0,0],BACK], endcap2="arrow2", width=.05);
 134//   right(.05)
 135//   color("black")move(BACK) text3d("BACK",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 136//   stroke([[0,0,0],DOWN], endcap2="arrow2", width=.05);
 137//   color("black")
 138//   right(.1){
 139//   up(.12)move(BOT) text3d("DOWN",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 140//   move(BOT) text3d("BOTTOM",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 141//   down(.12)move(BOT) text3d("BOT",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 142//   }
 143//   stroke([[0,0,0],TOP], endcap2="arrow2", width=.05);
 144//   color("black")left(.05){
 145//   up(.12)move(TOP) text3d("TOP",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 146//   move(TOP) text3d("UP",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 147//   }
 148// Figure(2D,Big): Named constants for direction vectors in 2D.  For anchors the TOP and BOTTOM directions are collapsed into 2D as shown here, but do not try to use TOP or BOTTOM as 2D directions in other situations.
 149//   $fn=12;
 150//   stroke(path2d([[0,0,0],RIGHT]), endcap2="arrow2", width=.05);
 151//   color("black")fwd(.22)left(.05)move(RIGHT) text("RIGHT",size=.1,anchor=RIGHT);
 152//   stroke(path2d([[0,0,0],LEFT]), endcap2="arrow2", width=.05);
 153//   color("black")right(.05)fwd(.22)move(LEFT) text("LEFT",size=.1,anchor=LEFT);
 154//   stroke(path2d([[0,0,0],FRONT]), endcap2="arrow2", width=.05);
 155//   color("black")
 156//   fwd(.2)
 157//   right(.15)
 158//   color("black")move(BACK) { text("BACK",size=.1,anchor=LEFT); back(.14) text("(TOP)", size=.1, anchor=LEFT);}
 159//   color("black")
 160//   left(.15)back(.2+.14)move(FRONT){
 161//   back(.14) text("FRONT",size=.1,anchor=RIGHT);
 162//       text("FWD",size=.1,anchor=RIGHT);
 163//   fwd(.14) text("FORWARD",size=.1,anchor=RIGHT);
 164//   fwd(.28) text("(BOTTOM)",size=.1,anchor=RIGHT);
 165//   fwd(.14*3) text("(BOT)",size=.1,anchor=RIGHT);
 166//   }
 167//   stroke(path2d([[0,0,0],BACK]), endcap2="arrow2", width=.05);
 168// Subsection: Specifying Faces
 169//   Modules operating on faces accept a list of faces to describe the faces to operate on.  Each
 170//   face is given by a vector that points to that face.  Attachments of cuboid objects onto their faces also
 171//   work by choosing an attachment face with a single vector in the same manner.
 172// Figure(3D,Big,NoScales,VPD=275): The six faces of the cube.  Some have faces have more than one name.
 173//   ydistribute(50) {
 174//      xdistribute(35){
 175//        _show_cube_faces([BACK], botlabel=["BACK"]);
 176//        _show_cube_faces([UP],botlabel=["TOP","UP"]);
 177//        _show_cube_faces([RIGHT],botlabel=["RIGHT"]);
 178//      }
 179//      xdistribute(35){
 180//        _show_cube_faces([FRONT],toplabel=["FRONT","FWD", "FORWARD"]);
 181//        _show_cube_faces([DOWN],toplabel=["BOTTOM","BOT","DOWN"]);
 182//        _show_cube_faces([LEFT],toplabel=["LEFT"]);
 183//      }
 184//   }
 185// Subsection: Specifying Edges
 186//   Modules operating on edges use two arguments to describe the edge set they will use: The `edges` argument
 187//   is a list of edge set descriptors to include in the edge set, and the `except` argument is a list of
 188//   edge set descriptors to remove from the edge set.
 189//   The default value for `edges` is `"ALL"`, the set of all edges.
 190//   The default value for `except` is the    empty set, meaning no edges are removed.
 191//   If either argument is just a single edge set
 192//   descriptor it can be passed directly rather than in a singleton list.
 193//   Each edge set descriptor must be one of:
 194//   - A vector pointing towards an edge, indicating that single edge.
 195//   - A vector pointing towards a face, indicating all edges surrounding that face.
 196//   - A vector pointing towards a corner, indicating all edges touching that corner.
 197//   - The string `"X"`, indicating all X axis aligned edges.
 198//   - The string `"Y"`, indicating all Y axis aligned edges.
 199//   - The string `"Z"`, indicating all Z axis aligned edges.
 200//   - The string `"ALL"`, indicating all edges.
 201//   - The string `"NONE"`, indicating no edges at all.
 202//   - A 3x4 array, where each entry corresponds to one of the 12 edges and is set to 1 if that edge is included and 0 if the edge is not.  The edge ordering is:
 203//       ```
 204//       [
 205//           [Y-Z-, Y+Z-, Y-Z+, Y+Z+],
 206//           [X-Z-, X+Z-, X-Z+, X+Z+],
 207//           [X-Y-, X+Y-, X-Y+, X+Y+]
 208//       ]
 209//       ```
 210//   .
 211//   You can specify edge descriptors directly by giving a vector, or you can use sums of the
 212//   named direction vectors described above.  Below we show all of the edge sets you can
 213//   describe with sums of the direction vectors, and then we show some examples of combining
 214//   edge set descriptors.
 215// Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward an edge select that single edge
 216//   ydistribute(50) {
 217//       xdistribute(30) {
 218//           _show_edges(edges=BOT+RIGHT);
 219//           _show_edges(edges=BOT+BACK);
 220//           _show_edges(edges=BOT+LEFT);
 221//           _show_edges(edges=BOT+FRONT);
 222//       }
 223//       xdistribute(30) {
 224//           _show_edges(edges=FWD+RIGHT);
 225//           _show_edges(edges=BACK+RIGHT);
 226//           _show_edges(edges=BACK+LEFT);
 227//           _show_edges(edges=FWD+LEFT);
 228//       }
 229//       xdistribute(30) {
 230//           _show_edges(edges=TOP+RIGHT);
 231//           _show_edges(edges=TOP+BACK);
 232//           _show_edges(edges=TOP+LEFT);
 233//           _show_edges(edges=TOP+FRONT);
 234//       }
 235//   }
 236// Figure(3D,Med,VPD=205,NoScales): Vectors pointing toward a face select all edges surrounding that face.
 237//   ydistribute(50) {
 238//       xdistribute(30) {
 239//           _show_edges(edges=LEFT);
 240//           _show_edges(edges=FRONT);
 241//           _show_edges(edges=RIGHT);
 242//       }
 243//       xdistribute(30) {
 244//           _show_edges(edges=TOP);
 245//           _show_edges(edges=BACK);
 246//           _show_edges(edges=BOTTOM);
 247//       }
 248//   }
 249// Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward a corner select all edges surrounding that corner.
 250//   ydistribute(50) {
 251//       xdistribute(30) {
 252//           _show_edges(edges=FRONT+LEFT+TOP);
 253//           _show_edges(edges=FRONT+RIGHT+TOP);
 254//           _show_edges(edges=FRONT+LEFT+BOT);
 255//           _show_edges(edges=FRONT+RIGHT+BOT);
 256//       }
 257//       xdistribute(30) {
 258//           _show_edges(edges=TOP+LEFT+BACK);
 259//           _show_edges(edges=TOP+RIGHT+BACK);
 260//           _show_edges(edges=BOT+LEFT+BACK);
 261//           _show_edges(edges=BOT+RIGHT+BACK);
 262//       }
 263//   }
 264// Figure(3D,Med,VPD=205,NoScales): Named Edge Sets
 265//   ydistribute(50) {
 266//       xdistribute(30) {
 267//           _show_edges(edges="X");
 268//           _show_edges(edges="Y");
 269//           _show_edges(edges="Z");
 270//       }
 271//       xdistribute(30) {
 272//           _show_edges(edges="ALL");
 273//           _show_edges(edges="NONE");
 274//       }
 275//   }
 276// Figure(3D,Big,VPD=310,NoScales):  Next are some examples showing how you can combine edge descriptors to obtain different edge sets.    You can specify the top front edge with a numerical vector or by combining the named direction vectors.  If you combine them as a list you get all the edges around the front and top faces.  Adding `except` removes an edge.
 277//   xdistribute(43){
 278//     _show_edges(_edges([0,-1,1]),toplabel=["edges=[0,-1,1]"]);
 279//     _show_edges(_edges(TOP+FRONT),toplabel=["edges=TOP+FRONT"]);
 280//     _show_edges(_edges([TOP,FRONT]),toplabel=["edges=[TOP,FRONT]"]);
 281//     _show_edges(_edges([TOP,FRONT],TOP+FRONT),toplabel=["edges=[TOP,FRONT]","except=TOP+FRONT"]);
 282//   }
 283// Figure(3D,Big,VPD=310,NoScales): Using `except=BACK` removes the four edges surrounding the back face if they are present in the edge set.  In the first example only one edge needs to be removed.  In the second example we remove two of the Z-aligned edges.  The third example removes all four back edges from the default edge set of all edges.  You can explicitly give `edges="ALL"` but it is not necessary, since this is the default.  In the fourth example, the edge set of Y-aligned edges contains no back edges, so the `except` parameter has no effect.
 284//   xdistribute(43){
 285//     _show_edges(_edges(BOT,BACK), toplabel=["edges=BOT","except=BACK"]);
 286//     _show_edges(_edges("Z",BACK), toplabel=["edges=\"Z\"", "except=BACK"]);
 287//     _show_edges(_edges("ALL",BACK), toplabel=["(edges=\"ALL\")", "except=BACK"]);
 288//     _show_edges(_edges("Y",BACK), toplabel=["edges=\"Y\"","except=BACK"]);
 289//   }
 290// Figure(3D,Big,NoScales,VPD=310): On the left `except` is a list to remove two edges.  In the center we show a corner edge set defined by a numerical vector, and at the right we remove that same corner edge set with named direction vectors.
 291//   xdistribute(52){
 292//    _show_edges(_edges("ALL",[FRONT+RIGHT,FRONT+LEFT]),
 293//               toplabel=["except=[FRONT+RIGHT,","       FRONT+LEFT]"]);
 294//    _show_edges(_edges([1,-1,1]),toplabel=["edges=[1,-1,1]"]);
 295//    _show_edges(_edges([TOP,BOT], TOP+RIGHT+FRONT),toplabel=["edges=[TOP,BOT]","except=TOP+RIGHT+FRONT"]);
 296//   }
 297// Subsection: Specifying Corners
 298//   Modules operating on corners use two arguments to describe the corner set they will use: The `corners` argument
 299//   is a list of corner set descriptors to include in the corner set, and the `except` argument is a list of
 300//   corner set descriptors to remove from the corner set.
 301//   The default value for `corners` is `"ALL"`, the set of all corners.
 302//   The default value for `except` is the   empty set, meaning no corners are removed.
 303//   If either argument is just a single corner set
 304//   descriptor it can be passed directly rather than in a singleton list.
 305//   Each corner set descriptor must be one of:
 306//   - A vector pointing towards a corner, indicating that corner.
 307//   - A vector pointing towards an edge indicating both corners at the ends of that edge.
 308//   - A vector pointing towards a face, indicating all the corners of that face.
 309//   - The string `"ALL"`, indicating all corners.
 310//   - The string `"NONE"`, indicating no corners at all.
 311//   - A length 8 vector where each entry corresponds to a corner and is 1 if the corner is included and 0 if it is excluded.  The corner ordering is
 312//       ```
 313//       [X-Y-Z-, X+Y-Z-, X-Y+Z-, X+Y+Z-, X-Y-Z+, X+Y-Z+, X-Y+Z+, X+Y+Z+]
 314//       ```
 315//   .
 316//   You can specify corner descriptors directly by giving a vector, or you can use sums of the
 317//   named direction vectors described above.  Below we show all of the corner sets you can
 318//   describe with sums of the direction vectors and then we show some examples of combining
 319//   corner set descriptors.
 320// Figure(3D,Big,NoScales,VPD=300): Vectors pointing toward a corner select that corner.
 321//   ydistribute(55) {
 322//       xdistribute(35) {
 323//           _show_corners(corners=FRONT+LEFT+TOP);
 324//           _show_corners(corners=FRONT+RIGHT+TOP);
 325//           _show_corners(corners=FRONT+LEFT+BOT);
 326//           _show_corners(corners=FRONT+RIGHT+BOT);
 327//       }
 328//       xdistribute(35) {
 329//           _show_corners(corners=TOP+LEFT+BACK);
 330//           _show_corners(corners=TOP+RIGHT+BACK);
 331//           _show_corners(corners=BOT+LEFT+BACK);
 332//           _show_corners(corners=BOT+RIGHT+BACK);
 333//       }
 334//   }
 335// Figure(3D,Big,NoScales,VPD=340): Vectors pointing toward an edge select the corners and the ends of the edge.
 336//   ydistribute(55) {
 337//       xdistribute(35) {
 338//           _show_corners(corners=BOT+RIGHT);
 339//           _show_corners(corners=BOT+BACK);
 340//           _show_corners(corners=BOT+LEFT);
 341//           _show_corners(corners=BOT+FRONT);
 342//       }
 343//       xdistribute(35) {
 344//           _show_corners(corners=FWD+RIGHT);
 345//           _show_corners(corners=BACK+RIGHT);
 346//           _show_corners(corners=BACK+LEFT);
 347//           _show_corners(corners=FWD+LEFT);
 348//       }
 349//       xdistribute(35) {
 350//           _show_corners(corners=TOP+RIGHT);
 351//           _show_corners(corners=TOP+BACK);
 352//           _show_corners(corners=TOP+LEFT);
 353//           _show_corners(corners=TOP+FRONT);
 354//       }
 355//   }
 356// Figure(3D,Med,NoScales,VPD=225): Vectors pointing toward a face select the corners of the face.
 357//   ydistribute(55) {
 358//       xdistribute(35) {
 359//           _show_corners(corners=LEFT);
 360//           _show_corners(corners=FRONT);
 361//           _show_corners(corners=RIGHT);
 362//       }
 363//       xdistribute(35) {
 364//           _show_corners(corners=TOP);
 365//           _show_corners(corners=BACK);
 366//           _show_corners(corners=BOTTOM);
 367//       }
 368//   }
 369// Figure(3D,Med,NoScales,VPD=200): Corners by name
 370//   xdistribute(35) {
 371//       _show_corners(corners="ALL");
 372//       _show_corners(corners="NONE");
 373//   }
 374// Figure(3D,Big,NoScales,VPD=300):     Next are some examples showing how you can combine corner descriptors to obtain different corner sets.   You can specify corner sets numerically or by adding together named directions.  The third example shows a list of two corner specifications, giving all the corners on the front face or the right face.
 375//   xdistribute(52){
 376//     _show_corners(_corners([1,-1,-1]),toplabel=["corners=[1,-1,-1]"]);
 377//     _show_corners(_corners(BOT+RIGHT+FRONT),toplabel=["corners=BOT+RIGHT+FRONT"]);
 378//     _show_corners(_corners([FRONT,RIGHT]), toplabel=["corners=[FRONT,RIGHT]"]);
 379//   }
 380// Figure(3D,Big,NoScales,VPD=300): Corners for one edge, two edges, and all the edges except the two on one edge.  Note that since the default is all edges, you only need to give the except argument in this case:
 381//    xdistribute(52){
 382//      _show_corners(_corners(FRONT+TOP), toplabel=["corners=FRONT+TOP"]);
 383//       _show_corners(_corners([FRONT+TOP,BOT+BACK]), toplabel=["corners=[FRONT+TOP,","        BOT+BACK]"]);
 384//       _show_corners(_corners("ALL",FRONT+TOP), toplabel=["(corners=\"ALL\")","except=FRONT+TOP"]);
 385//    }
 386// Figure(3D,Med,NoScales,VPD=240): The first example shows a single corner removed from the top corners using a numerical vector.  The second one shows removing a set of two corner descriptors from the implied set of all corners.
 387//    xdistribute(58){
 388//       _show_corners(_corners(TOP,[1,1,1]), toplabel=["corners=TOP","except=[1,1,1]"]);
 389//       _show_corners(_corners("ALL",[FRONT+RIGHT+TOP,FRONT+LEFT+BOT]),
 390//                    toplabel=["except=[FRONT+RIGHT+TOP,","       FRONT+LEFT+BOT]"]);
 391//    }
 392// Subsection: Anchoring of Non-Rectangular Objects and Anchor Type (atype)
 393//   We focused above on rectangular objects that have well-defined faces and edges aligned with the coordinate axes.
 394//   Things get difficult when the objects are curved, or even when their edges are not neatly aligned with the coordinate axes.
 395//   In these cases, the library may provide multiple different anchoring schemes, called the anchor types.  When a module supports
 396//   multiple anchor types, use the `atype=` parameter to select the anchor type you need.
 397// .
 398//   First consider the case of a simple rectangle whose corners have been rounded.  Where should the anchors lie?
 399//   The default anchor type puts them in the same location as the anchors of an unrounded rectangle, which means that for
 400//   positive rounding radii, they are not even located on the perimeter of the object.
 401// Figure(2D,Med,NoAxes): Default "box" atype anchors for a rounded {{rect()}}
 402//   rect([100,50], rounding=[10,0,0,-20],chamfer=[0,10,-20,0]) show_anchors();
 403// Continues:
 404//   This choice enables you to position the box, or attach things to it, without regard to its rounding or chamfers.  If you need to
 405//   anchor onto the roundovers or chamfers then you can use the "perim" anchor type:
 406// Figure(2D,Med,NoAxes): The "perim" atype for a rounded and chamfered {{rect()}}
 407//   rect([100,50], rounding=[10,0,0,-20],chamfer=[0,10,-20,0],atype="perim") show_anchors();
 408// Continues:
 409//   With this anchor type, the anchors are located on the perimeter.  For positive roundings they point in the standard anchor direction;
 410//   for negative roundings they are parallel to the base.  As noted above, for circles, cylinders, and spheres, the anchor point is
 411//   determined by choosing the point where the anchor vector intersects the shape.  On a circle, this results in an anchor whose direction
 412//   matches the user provided anchor vector.  But on an ellipse, something else happens:
 413// Figure(2D,Med,NoAxes): Anchors on an ellipse.  The red arrow shows a TOP+RIGHT anchor direction. 
 414//   ellipse([70,30]) show_anchors();
 415//   stroke([[0,0],[45,45]], color="red",endcap2="arrow2");
 416// Continues:
 417//   For a TOP+RIGHT anchor direction, the surface normal at the intersection point does not match the anchor direction,
 418//   so the direction of the anchor shown in blue does not match the direction specified, in red.
 419//   Anchors computed this way have anchor type "intersect".  When a shape is concave, intersection anchors can produce
 420//   a result buried inside the shape's concavity.  Consider the RIGHT anchor of this supershape example:
 421// Figure(2D,Med,NoAxes): A supershape with "intersect" anchor type:
 422//   supershape(n=150,r=75, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9,atype="intersect") show_anchors();
 423// Continues:
 424//   A different anchor type called "hull" finds anchors that are on the convex hull of the shape.  
 425// Figure(2D,Med,NoAxes): A supershape with "hull" anchor type:
 426//   supershape(n=150,r=55, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9,atype="hull") show_anchors();
 427// Continues:
 428//   Hull anchoring works by creating the line (or plane in 3D) that is normal to the specified anchor direction, and
 429//   finding the point farthest from the center that intersects that line (or plane).
 430// Figure(2D,Med,NoAxes): Finding the RIGHT and BACK+LEFT "hull" anchors
 431//   supershape(n=128,r=55, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9,atype="hull") {
 432//     position(RIGHT) color_this("red")rect([1,90],anchor=LEFT);
 433//     attach(RIGHT)anchor_arrow2d(13);
 434//     attach(BACK+LEFT) {
 435//        anchor_arrow2d(13);
 436//        color_this("red")rect([30,1]);
 437//        }
 438//     }
 439// Continues:
 440//   In the example the RIGHT anchor is found when the normal line (shown in red) is tangent to the shape at two points.
 441//   The anchor is then taken to be the midpoint.  The BACK+LEFT anchor occurs with a single tangent point, and the
 442//   anchor point is located at the tangent point.  For circles intersection is done to the exact circle, but for other
 443//   shapes these calculations are done on the point lists that defines the shape, so if you change the number of points
 444//   in the list, the precise location of the anchors can change.  You can also get surprising results if your point list is badly chosen.
 445// Figure(2D,Med,NoAxes): Circle anchor in blue.  The red anchor is computed to a point list of a circle with 17 segments.  
 446//   circle(r=31,$fn=128) attach(TOP)anchor_arrow2d(15);
 447//   region(circle(r=33,$fn=17)) {color("red")attach(TOP)anchor_arrow2d(13);}
 448// Continues:
 449//   The figure shows a large horizontal offset due to a poor choice of sampling for the circular shape when using the "hull" anchor type.
 450//   The determination of "hull" or "intersect" anchors may depend on the location of the centerpoint used in the computation.
 451//   Some of the modules allow you to change the centerpoint using a `cp=` argument.  If you need to change the centerpoint for
 452//   a module that does not provide this option, you can use the generic {{region()}} module, which will let you specify a centerpoint.
 453//   The default center point is the centroid, specified by "centroid".  You can also choose "mean", which gives the mean of all
 454//   the data points, or "bbox", which gives the centerpoint of the bounding box for the data.  Your last option for centerpoint is to
 455//   choose an arbitrary point that meets your needs.
 456// Figure(2D,Med,NoAxes): The centerpoint for "intersect" anchors is located at the red dot
 457//   region(supershape(n=128,r=55, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9),atype="intersect",cp=[0,30]) show_anchors();
 458//   color("red")back(30)circle(r=2,$fn=16);
 459// Continues:
 460//   Note that all the anchors for an object have to be determined based on one anchor type and relative to the same centerpoint.
 461//   The supported anchor types for each module appear in the "Anchor Types" section of its entry.  
 462
 463
 464
 465
 466
 467// Section: Attachment Positioning
 468
 469// Module: position()
 470// Synopsis: Attaches children to a parent object at an anchor point.
 471// SynTags: Trans
 472// Topics: Attachments
 473// See Also: attachable(), attach(), orient()
 474// Usage:
 475//   PARENT() position(at) CHILDREN;
 476// Description:
 477//   Attaches children to a parent object at an anchor point.  For a step-by-step explanation
 478//   of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 479// Arguments:
 480//   at = The vector, or name of the parent anchor point to attach to.
 481// Side Effects:
 482//   `$attach_anchor` for each `from=` anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
 483//   `$attach_to` is set to `undef`.
 484// Example:
 485//   spheroid(d=20) {
 486//       position(TOP) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
 487//       position(RIGHT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
 488//       position(FRONT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
 489//   }
 490module position(at,from)
 491{
 492    if (is_def(from)){
 493      echo("'from' argument of position() has changed to 'at' and will be removed in a future version");
 494    }
 495    dummy0=assert(num_defined([at,from])==1, "Cannot give both `at` argument and the deprectated `from` argument to position()");
 496    at = first_defined([at,from]);
 497    req_children($children);
 498    dummy1=assert($parent_geom != undef, "No object to position relative to.");
 499    anchors = (is_vector(at)||is_string(at))? [at] : at;
 500    two_d = _attach_geom_2d($parent_geom);
 501    for (anchr = anchors) {
 502        anch = _find_anchor(anchr, $parent_geom);
 503        $attach_to = undef;
 504        $attach_anchor = anch;
 505        translate(anch[1]) children();
 506    }
 507}
 508
 509
 510
 511// Module: orient()
 512// Synopsis: Orients children's tops in the directon of the specified anchor.
 513// SynTags: Trans
 514// Topics: Attachments
 515// See Also: attachable(), attach(), position()
 516// Usage:
 517//   PARENT() orient(anchor, [spin]) CHILDREN;
 518// Description:
 519//   Orients children such that their top is tilted in the direction of the specified parent anchor point. 
 520//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 521// Arguments:
 522//   anchor = The anchor on the parent which you want to match the orientation of.
 523//   spin = The spin to add to the children.  (Overrides anchor spin.)
 524// Side Effects:
 525//   `$attach_to` is set to `undef`.
 526// Example: When orienting to an anchor, the spin of the anchor may cause confusion:
 527//   prismoid([50,50],[30,30],h=40) {
 528//       position(TOP+RIGHT)
 529//           orient(RIGHT)
 530//               prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
 531//   }
 532// Example: You can override anchor spin with `spin=`.
 533//   prismoid([50,50],[30,30],h=40) {
 534//       position(TOP+RIGHT)
 535//           orient(RIGHT,spin=0)
 536//               prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
 537//   }
 538// Example: Or you can anchor the child from the back
 539//   prismoid([50,50],[30,30],h=40) {
 540//       position(TOP+RIGHT)
 541//           orient(RIGHT)
 542//               prismoid([30,30],[0,5],h=20,anchor=BOT+BACK);
 543//   }
 544module orient(anchor, spin) {
 545    req_children($children);
 546    check=
 547      assert($parent_geom != undef, "No parent to orient from!")
 548      assert(is_string(anchor) || is_vector(anchor));
 549    anch = _find_anchor(anchor, $parent_geom);
 550    two_d = _attach_geom_2d($parent_geom);
 551    fromvec = two_d? BACK : UP;
 552    spin = default(spin, anch[3]);
 553    dummy=assert(is_finite(spin));
 554
 555    $attach_to = undef;
 556    if (two_d)
 557        rot(spin)rot(from=fromvec, to=anch[2]) children();
 558    else
 559        rot(spin, from=fromvec, to=anch[2]) children();
 560}
 561
 562
 563// Module: align()
 564// Synopsis: Position children with alignment to parent edges.
 565// SynTags: Trans
 566// Topics: Attachments
 567// See Also: attachable(), attach(), position(), orient()
 568// Usage:
 569//   PARENT() align(anchor, [align], [inside=], [inset=], [shiftout=], [overlap=]) CHILDREN;
 570// Description:
 571//   Place a child on the face identified by `anchor`.  If align is not given or is CENTER
 572//   then the child will be centered on top of the specified face, outside the parent object.  The align parameter is a
 573//   direction defining an edge or corner to align to.  The child will be aligned to that edge or corner by
 574//   choosing an appropriate anchor on the child.  
 575//   Like {{position()}} this module never rotates the child.  If you give `anchor=RIGHT` then the child
 576//   will be given the LEFT anchor and placed adjacent to the parent.  You can use `orient=` or `spin=`
 577//   with the child and the alignment will adjust to select the correct child anchor.  Note that if
 578//   you spin the child by an amount not a multiple of 90 degrees then an edge of the child will be
 579//   placed against the parent.  This module makes it easy to place children aligned flush with the edges
 580//   of the parent, even after orienting them or spinning them.  In contrast {{position()}} can 
 581//   do the same thing but you would have to figure out the correct child anchor, which is not always obvious.
 582//   .
 583//   Because `align()` works by setting the child anchor, it overrides any anchor you specify to the child:
 584//   **any `anchor=` value given to the child is ignored.**
 585//   .
 586//   Several options can adjust how the child is positioned.  You can specify `inset=` to inset the
 587//   aligned object from its alignment location. If you set `inside=true` then the
 588//   child will appear inside the parent instead of on its surface so that you can use {{diff()}} to subract it.
 589//   In this case the child recieved a default "remove" tag.   The `shiftout=` option works with `inside=true` to 
 590//   shift the child out by the specified distance so that the child doesn't exactly align with the parent.
 591//   .
 592//   Note that in the description above the anchor was said to define a "face".  You can also use this module
 593//   with an edge anchor, in which case a corner of the child will be placed in contact with the specified
 594//   edge and the align direction will shift the child to either end of the edge.  You can even give a
 595//   corner as the anchor point, but in that case the only allowed alignment is CENTER.
 596//   .
 597//   If you give a list of anchors and/or a list of align directions then all combinations are generated.
 598//   In this way align() acts like a distributor, creating multiple copies of the child.  
 599//   Named anchors are not supported by `align()`.  
 600// Arguments:
 601//   anchor = parent anchor or list of parent anchors for positioning children.
 602//   align = optional alignment direction or directions for aligning the children.  Default: CENTER
 603//   ---
 604//   inside = if true, place object inside the parent instead of outside.  Default: false
 605//   inset = shift the child away from the alignment edge/corner by this amount.  Default: 0
 606//   shiftout = Shift an inside object outward so that it overlaps all the aligned faces.  Default: 0
 607//   overlap = Amount to sink the child into the parent.  Defaults to `$overlap` which is zero by default.
 608// Side Effects:
 609//   `$anchor` set to the anchor value used for the child.
 610//   `$align` set to the align value used for the child.
 611//   `$idx` set to a unique index for each child, increasing by alignment first.
 612//   `$attach_anchor` for each anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
 613//   if inside is true then set default tag to "remove"
 614// Example:  Cuboid positioned on the right of its parent.  Note that it is in its native orientation.  
 615//   cuboid([20,35,25])
 616//     align(RIGHT)
 617//       color("lightgreen")cuboid([5,1,9]);
 618// Example: Child would require anchor of RIGHT+FRONT+BOT if placed with {{position()}}. 
 619//   cuboid([50,40,15])
 620//     align(TOP,RIGHT+FRONT)
 621//       color("lightblue")prismoid([10,5],[7,4],height=4);
 622// Example: Child requires a different anchor for each position, so a simple explicit specification of the anchor for children is impossible in this case, without using two separate commands.
 623//   cuboid([50,40,15])
 624//     align(TOP,[RIGHT,LEFT])
 625//       color("lightblue")prismoid([10,5],[7,4],height=4);
 626// Example: If you spin the child 90 deg it is still flush with the edge of the parent.  In this case the required anchor for the child is BOT+FWD:
 627//   cuboid([50,40,15])
 628//     align(TOP,RIGHT)
 629//       color("lightblue")
 630//          prismoid([10,5],[7,4],height=4,spin=90);
 631// Example: Here the child is placed on the RIGHT face.  Notice how the TOP+LEFT anchor of the prismoid is aligned with the edge of the parent.  The prismoid remains in the same orientation.  
 632//   cuboid([50,40,15])
 633//     align(RIGHT,TOP)
 634//       color("lightblue")prismoid([10,5],[7,4],height=4);
 635// Example: If you change the orientation of the child it still appears aligned flush in its changed orientation:
 636//   cuboid([50,40,15])
 637//     align(TOP, RIGHT)
 638//       color("lightblue")prismoid([10,5],[7,4],height=4,orient=DOWN);
 639// Example: The center of the cubes edge is lined up with the center of the prismoid edge, so this result is the expected result:  
 640//   prismoid(50,30,25)
 641//     align(RIGHT,FRONT)
 642//       color("lightblue")cuboid(8);
 643// Example: Spinning the cube means that the corner of the cube is the most extreme point, so that's what aligns with the front edge of the parent:
 644//   cuboid([50,40,15])
 645//     align(TOP,FWD)
 646//       color("lightblue")cuboid(9,spin=22);
 647// Example: A similar thing happens if you attach a cube to a cylinder with an arbitrary anchor angle:
 648//   cyl(h=20,d=10,$fn=128)
 649//     align([1,.3],TOP)
 650//       color("lightblue")cuboid(5);
 651// Example: Orienting the child is done in the global coordinate system (as usual) not in the parent coordinate system.  Note that the blue prismoid is not lined up with the parent face.  (To place the child on the face use {{attach()}}.
 652//   prismoid(50,30,25)
 653//     align(RIGHT)
 654//      color("lightblue")prismoid([10,5],[7,4],height=4,orient=RIGHT);
 655// Example: Setting `inside=true` enables us to subtract the child from the parent with {{diff()}}.  The "remove" tag is automatically applied when you set `inside=true`, and we used `shiftout=0.01` to prevent z-fighting on the faces.  
 656//   diff()
 657//     cuboid([40,30,10])
 658//       align(FRONT,TOP,inside=true,shiftout=0.01)
 659//         prismoid([10,5],[7,5],height=4);
 660// Example: Setting inset shifts all of the children away from their aligned edge, which is a different direction for each child.  
 661//   cuboid([40,30,30])
 662//     align(FRONT,[TOP,BOT,LEFT,RIGHT,TOP+RIGHT,BOT+LEFT], inset=3)
 663//       color("green") cuboid(2);
 664// Example: Changing the child characteristics based on the alignment
 665//   cuboid([20,20,8])
 666//     align(TOP,[for(i=[-1:1], j=[-1:1]) [i,j]])
 667//       color("orange")
 668//         if (norm($align)==0) cuboid([3,3,1]);
 669//         else if (norm($align)==norm([1,1])) cuboid([3,3,4.5]);
 670//         else cuboid(3);
 671// Example:  In this example the pink cubes are positioned onto an edge.  They meet edge-to-edge.  Aligning left shifts the cube to the left end of the edge. 
 672//   cuboid([30,30,20])
 673//      align(TOP+BACK,[CTR,LEFT])
 674//        color("pink")cuboid(4);
 675// Example: Normally `overlap` is used to create a tiny overlap to keep CGAL happy, but you can also give it a large value as shown here:
 676//   cuboid([30,30,20])
 677//     align(TOP+BACK,[RIGHT,CTR,LEFT],overlap=2)
 678//       color("lightblue")cuboid(4);
 679
 680module align(anchor,align=CENTER,inside=false,inset=0,shiftout=0,overlap)
 681{
 682    req_children($children);
 683    overlap = (overlap!=undef)? overlap : $overlap;
 684    dummy1=assert($parent_geom != undef, "No object to align to.")
 685           assert(is_undef($attach_to), "Cannot use align() as a child of attach()");
 686    if (is_undef($align_msg) || $align_msg)
 687        echo("ALERT: align() has changed, May 1, 2024.  See the wiki and attach(align=). $align_msg=false disables this message");
 688    anchor = is_vector(anchor) ? [anchor] : anchor;
 689    align = is_vector(align) ? [align] : align;
 690    two_d = _attach_geom_2d($parent_geom);
 691    factor = inside?-1:1;
 692    for (i = idx(anchor)) {
 693        $align_msg=false;     // Remove me when removing the message above
 694        face = anchor[i];
 695        $anchor=face;
 696        dummy=
 697          assert(!is_string(face),
 698                 str("Named anchor \"",face,"\" given for anchor, but align() does not support named anchors"))
 699          assert(is_vector(face) && (len(face)==2 || len(face)==3),
 700                 str("Invalid face ",face, ".  Must be a 2-vector or 3-vector"));
 701        thisface = two_d? _force_anchor_2d(face) : point3d(face);
 702        for(j = idx(align)) {
 703          edge=align[j];
 704          $idx = j+len(align)*i;
 705          $align=edge;
 706          dummy1=assert(is_vector(edge) && (len(edge)==2 || len(edge)==3),
 707                        "align direction must be a 2-vector or 3-vector");
 708          thisedge = two_d? _force_anchor_2d(edge) : point3d(edge);
 709          dummy=assert(all_zero(v_mul(thisedge,thisface)),
 710                       str("align (",thisedge,") cannot include component parallel to anchor ",thisface));
 711          thisface_anch = _find_anchor(thisface, $parent_geom);
 712          inset_dir = two_d ? -thisface
 713                    : unit(thisface_anch[1]-_find_anchor([thisedge.x,0,0]+thisface, $parent_geom)[1],CTR)
 714                       +unit(thisface_anch[1]-_find_anchor([0,thisedge.y,0]+thisface, $parent_geom)[1],CTR)
 715                       +unit(thisface_anch[1]-_find_anchor([0,0,thisedge.z]+thisface, $parent_geom)[1],CTR);
 716          
 717          pos_anch = _find_anchor(thisface+thisedge, $parent_geom);
 718          $attach_alignment = thisedge-factor*thisface;
 719          $attach_anchor=list_set(pos_anch,2,UP);
 720          translate(pos_anch[1]
 721                    +inset*inset_dir
 722                    +shiftout*(thisface_anch[2]-inset_dir)
 723                    -overlap*thisface_anch[2])
 724              default_tag("remove",inside) children();                  
 725        }
 726    }
 727}
 728
 729// Quantize anchor entry to {-1,0,1}
 730function _quant_anch(x) = approx(x,0) ? 0 : sign(x);
 731
 732// Make arbitrary anchor legal for a given geometry
 733function _make_anchor_legal(anchor,geom) =
 734   in_list(geom[0], ["prismoid","trapezoid"]) ? [for(v=anchor) _quant_anch(v)]
 735 : in_list(geom[0], ["conoid", "extrusion_extent"]) ? [anchor.x,anchor.y, _quant_anch(anchor.z)]
 736 : anchor;
 737    
 738
 739
 740// Module: attach()
 741// Synopsis: Attaches children to a parent object at an anchor point and with anchor orientation.
 742// SynTags: Trans
 743// Topics: Attachments
 744// See Also: attachable(), position(), align(), face_profile(), edge_profile(), corner_profile()
 745// Usage:
 746//   PARENT() attach(parent, child, [align=], [spin=], [overlap=], [inside=], [inset=], [shiftout=]) CHILDREN;
 747//   PARENT() attach(parent, [overlap=], [spin=]) CHILDREN;
 748// Description:
 749//   Attaches children to a parent object at an anchor point or points, oriented in the anchor direction.
 750//   This module differs from {{position()}} and {{align()}} in that it rotates the children to
 751//   the anchor direction, which generally means it places the children on the surface of a parent.
 752//   There are two modes of operation, parent anchor (single argument) and parent-child anchor (double argument).
 753//   .
 754//   The parent-child anchor (double argument) version is usually easier to use, and it is more powerful because it supports
 755//   alignment.  You provide an anchor on the parent (`parent`) and an anchor on the child (`child`).
 756//   This module connects the `child` anchor on the child to the `parent` anchor on the parent.  
 757//   Imagine pointing the parent and child anchor arrows at each other and pushing the objects
 758//   together until they meet at the anchor point.    The most basic case
 759//   is `attach(TOP,BOT)` which puts the bottom of the child onto the top of the parent.  If you
 760//   do `attach(RIGHT,BOT)` this puts the bottom of the child onto the right anchor of the parent.
 761//   When an object is attached to the top or bottom its BACK direction will remaing pointing BACK.
 762//   When an object is attached to one of the other anchors its FRONT will be pointed DOWN and its
 763//   BACK pointed UP.  You can change this using the `spin=` argument to attach().  Note that this spin
 764//   rotates around the attachment vector and is not the same as the spin argument to the child, which
 765//   will usually rotate around some other direction that may be hard to predict.  For 2D objects you cannot
 766//   give spin because it is not possible to spin around the attachment vector; spinning the object around the Z axis
 767//   would change the child orientation so that the anchors are no longer parallel.  Furthermore, any spin
 768//   parameter you give to the child will be ignored so that the attachment condition of parallel anchors is preserved.  
 769//   .
 770//   As with {{align()}} you can use the `align=` parameter to align the child to an edge or corner of the
 771//   face where that child is attached.  For example `attach(TOP,BOT,align=RIGHT)` would stand the child
 772//   up on the top while aligning it with the right edge of the top face, and `attach(RIGHT,BOT,align=TOP)` which
 773//   stand the object on the right face while aligning with the top edge.  If you apply spin using the
 774//   argument to `attach()` then it will be taken into account for the alignment.  If you apply spin with
 775//   a parameter to the child it will NOT be taken into account.  Note that spin is not permitted for
 776//   2D objects because it would change the child orientation so that the anchors are no longer parallel.  
 777//   When you use `align=` you can also adjust the position using `inset=`, which shifts the child
 778//   away from the edge or corner it is aligned to.  
 779//   .
 780//   If you give `inside=true` then the anchor arrows are lined up so they are pointing the same direction and
 781//   the child object will be located inside the parent.  In this case a default "remove" tag is applied to
 782//   the children.
 783//   .
 784//   Because the attachment process forces an orientation and anchor point for the child, it overrides
 785//   any such specifications you give to the child:  **both `anchor=` and `orient=` given to the child are
 786//   ignored** with the **double argument** version of `attach()`.  As noted above, you can give `spin=` to the
 787//   child but using the `spin=` parameter to `attach()` is more likely to be useful.
 788//   .
 789//   For the single parameter version of `attach()` you give only the `parent` anchor.  The `align` direction
 790//   is not permitted.  In this case the child is placed at the specified parent anchor point
 791//   and rotated to the anchor direction.  For example, `attach(TOP) cuboid(2);` will place a small
 792//   cube **with its center** located at the TOP anchor of the parent, so just half the cube will project
 793//   from the parent.  If you want the cube sitting on the parent you need to anchor the cube to its bottom:
 794//   `attach(TOP) cuboid(2,anchor=BOT);`.
 795//   .
 796//   The **single argument** version of `attach()` **respects `anchor=` and `orient=` given to the child.**
 797//   These options will probably be necessary, in fact, to get the child correctly positioned.  Note that
 798//   giving `spin=` to `attach()` in this case is the same as applying `zrot()` to the child. 
 799//   .
 800//   Attached children may be ovarlapped into the parent a bit, as given by the `$overlap` value
 801//   which is 0 by default, or by the `overlap=` argument.    This is to prevent OpenSCAD
 802//   from making non-manifold objects.  You can define `$overlap=` as an argument in a parent
 803//   module to set the default for all attachments to it.
 804//   .
 805//   For a step-by-step explanation of
 806//   attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 807// Arguments:
 808//   parent = The parent anchor point to attach to or a list of parent anchor points.
 809//   child = Optional child anchor point.  If given, orients the child to connect this anchor point to the parent anchor.
 810//   ---
 811//   align = If `child` is given you can specify alignment or list of alistnments to shift the child to an edge or corner of the parent. 
 812//   inset = Shift aligned children away from their alignment edge/corner by this amount.  Default: 0
 813//   overlap = Amount to sink child into the parent.  Equivalent to `down(X)` after the attach.  This defaults to the value in `$overlap`, which is `0` by default.
 814//   inside = If `child` is given you can set `inside=true` to attach the child to the inside of the parent for diff() operations.  Default: false
 815//   shiftout = Shift an inside object outward so that it overlaps all the aligned faces.  Default: 0
 816//   spin = Amount to rotate the parent around the axis of the parent anchor.  (Only permitted in 3D.)
 817// Side Effects:
 818//   `$anchor` set to the parent anchor value used for the child.
 819//   `$align` set to the align value used for the child.  
 820//   `$idx` set to a unique index for each child, increasing by alignment first.
 821//   `$attach_anchor` for each anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
 822//   if inside is true then set default tag to "remove"
 823//   `$attach_to` is set to the value of the `child` argument, if given.  Otherwise, `undef`
 824// Example: Cylinder placed on top of cube:
 825//   cuboid(50)
 826//     attach(TOP,BOT) cylinder(d1=30,d2=15,h=25);
 827// Example: Cylinder on right and front side of cube:
 828//   cuboid(50)
 829//     attach([RIGHT,FRONT],BOT) cylinder(d1=30,d2=15,h=25);
 830// Example:  Using `align` can align child object(s) with edges
 831//   prismoid(50,25,25) color("green"){
 832//     attach(TOP,BOT,align=[BACK,FWD]) cuboid(4);
 833//     attach(RIGHT,BOT,align=[TOP,BOT]) cuboid(4);
 834//   }
 835// Example: One aligned to the corner upside down (light blue) and one inset fromt the corner (pink), one aligned on a side (orange) and one rotated and aligned (green).
 836//   cuboid(30) {
 837//     attach(TOP,TOP,align=FRONT+RIGHT) color("lightblue") prismoid(5,3,3);
 838//     attach(TOP,BOT,inset=3,align=FRONT+LEFT) color("pink") prismoid(5,3,3);
 839//     attach(FRONT,RIGHT,align=TOP) color("orange") prismoid(5,3,3);
 840//     attach(FRONT,RIGHT,align=RIGHT,spin=90) color("lightgreen") prismoid(5,3,3);    
 841//   }
 842// Example: Rotation not a multiple of 90 degrees with alignment.  The children are aligned on a corner.  
 843//   cuboid(30)
 844//     attach(FRONT,BOT,spin=33,align=[RIGHT,LEFT,TOP,BOT,RIGHT+TOP])
 845//       color("lightblue")cuboid(4);
 846// Example: Anchoring the cone onto the sphere gives a single point of contact. 
 847//   spheroid(d=20) 
 848//       attach([1,1.5,1], BOTTOM) cyl(l=11.5, d1=10, d2=5);
 849// Example: Using the `overlap` option can help:
 850//   spheroid(d=20) 
 851//       attach([1,1.5,1], BOTTOM, overlap=1.5) cyl(l=11.5, d1=10, d2=5);
 852// Example: Alignment works for cylinders but you can only align with either the top or bototm face:
 853//   cyl(h=30,d=10)
 854//     attach([LEFT,[1,1.3]], BOT,align=TOP) cuboid(6);
 855// Example: Attaching to edges.  The light blue and orange objects are attached to edges.  The purple object is attached to an edge and aligned. 
 856//   prismoid([20,10],[10,10],7){
 857//     attach(RIGHT+TOP,BOT,align=FRONT) color("pink")cuboid(2);
 858//     attach(BACK+TOP, BOT) color("lightblue")cuboid(2);
 859//     attach(RIGHT+BOT, RIGHT,spin=90) color("orange")cyl(h=8,d=1);
 860//   }
 861// Example: Attaching inside the parent.  For inside attachment the anchors are lined up pointing the same direction, so the most natural way to anchor the child is using its TOP anchor.  This is equivalent to anchoring outside with the BOTTOM anchor and then lowering the child into the parent by its full depth.  
 862//   back_half()
 863//     diff()
 864//     cuboid(20)
 865//       attach(TOP,TOP,inside=true,shiftout=0.01) cyl(d1=10,d2=5,h=10);
 866// Example: Attaching inside the parent with alignment
 867//   diff()
 868//   cuboid(20){
 869//     attach(TOP,TOP,inside=true,align=RIGHT,shiftout=.01) cuboid([8,7,3]);
 870//     attach(TOP,TOP,inside=true,align=LEFT+FRONT,shiftout=0.01) cuboid([3,4,5]);
 871//     attach(RIGHT+FRONT, TOP, inside=true) cuboid([10,3,5]);
 872//     attach(RIGHT+FRONT, TOP, inside=true, align=TOP,shiftout=.01) cuboid([5,1,2]);  
 873//   }
 874
 875module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, inside=false, from, to)
 876{
 877    dummy3=
 878      assert(num_defined([to,child])<2, "Cannot combine deprecated 'to' argument with 'child' parameter")
 879      assert(num_defined([from,parent])<2, "Cannot combine deprecated 'from' argument with 'parent' parameter");
 880    if (is_def(to))
 881      echo("The 'to' option to attach() is deprecated and will be removed in the future.  Use 'child' instead.");
 882    if (is_def(from))
 883      echo("The 'from' option to attach(0 is deprecated and will be removed in the future.  Use 'parent' instead");
 884    if (norot)
 885      echo("The 'norot' option to attach() is deprecated and will be removed in the future.  Use position() instead.");
 886    req_children($children);
 887    
 888    dummy=assert($parent_geom != undef, "No object to attach to!")
 889          assert(is_undef(child) || is_string(child) || (is_vector(child) && (len(child)==2 || len(child)==3)), "child must be a named anchor (a string) or a 2-vector or 3-vector")
 890          assert(is_undef(align) || !is_string(child), "child is a named anchor.  Named anchors are not supported with align=");
 891          
 892    two_d = _attach_geom_2d($parent_geom);
 893    overlap = (overlap!=undef)? overlap : $overlap;
 894    parent = first_defined([parent,from]);
 895    anchors = is_vector(parent) || is_string(parent) ? [parent] : parent;
 896    align_list = is_undef(align) ? [undef]
 897               : is_vector(align) || is_string(align) ? [align] : align;
 898    dummy4 = assert(is_string(parent) || is_list(parent), "Invalid parent anchor or anchor list")
 899             assert(spin==0 || (!two_d || is_undef(child)), "spin is not allowed for 2d objects when 'child' is given");
 900    child_temp = first_defined([child,to]);
 901    child = two_d ? _force_anchor_2d(child_temp) : child_temp;
 902    dummy2=assert(align_list==[undef] || is_def(child), "Cannot use 'align' without 'child'")
 903           assert(!inside || is_def(child), "Cannot use 'inside' without 'child'")
 904           assert(inset==0 || is_def(child), "Cannot specify 'inset' without 'child'")
 905           assert(shiftout==0 || is_def(child), "Cannot specify 'shiftout' without 'child'");
 906    factor = inside?-1:1;
 907    $attach_to = child;
 908    for (anch_ind = idx(anchors)) {
 909        dummy=assert(is_string(anchors[anch_ind]) || (is_vector(anchors[anch_ind]) && (len(anchors[anch_ind])==2 || len(anchors[anch_ind])==3)),
 910                     str("parent[",anch_ind,"] is ",anchors[anch_ind]," but it must be a named anchor (string) or a 2-vector or 3-vector"))
 911              assert(align_list==[undef] || !is_string(anchors[anch_ind]),
 912                     str("parent[",anch_ind,"] is a named anchor (",anchors[anch_ind],"), but named anchors are not supported with align="));
 913        anchor = is_string(anchors[anch_ind])? anchors[anch_ind]
 914               : two_d?_force_anchor_2d(anchors[anch_ind])
 915               : point3d(anchors[anch_ind]);
 916        anchor_data = _find_anchor(anchor, $parent_geom);
 917        anchor_pos = anchor_data[1];
 918        anchor_dir = factor*anchor_data[2];
 919        anchor_spin = !inside || anchor_data[3]==0 ? anchor_data[3] : 180+anchor_data[3];
 920        $anchor=anchor;
 921        for(align_ind = idx(align_list)){
 922            align = is_undef(align_list[align_ind]) ? undef
 923                  : assert(is_vector(align_list[align_ind],2) || is_vector(align_list[align_ind],3), "align direction must be a 2-vector or 3-vector")
 924                    two_d ? _force_anchor_2d(align_list[align_ind])
 925                  : point3d(align_list[align_ind]);
 926            $idx = align_ind+len(align_list)*anch_ind;
 927            $align=align;
 928            dummy=assert(is_undef(align) || all_zero(v_mul(anchor,align)),
 929                         str("Invalid alignment: align value (",align,") includes component parallel to parent anchor (",anchor,")"));
 930            pos = is_undef(align) ? anchor_data[1] : _find_anchor(anchor+align, $parent_geom)[1];
 931            $attach_anchor = list_set(anchor_data, 1, pos);      ///
 932            startdir = two_d || is_undef(align)? undef
 933                     : anchor==UP || anchor==DOWN ? BACK
 934                     : UP - (anchor*UP)*anchor/(anchor*anchor);
 935            enddir = is_undef(child) || child.z==0 ? UP : BACK;
 936            child_adjustment = is_undef(align)? CTR
 937                              : two_d ? rot(to=child,from=-factor*anchor,p=align)
 938                              : apply( frame_map(x=child, z=enddir)
 939                                      *frame_map(x=-factor*anchor, z=startdir, reverse=true)
 940                                      *rot(v=anchor,-spin), align);
 941            $anchor_override = all_zero(child_adjustment)? inside?child:undef
 942                             : child+child_adjustment;
 943            reference = two_d? BACK : UP;
 944            inset_dir = is_undef(align) ? CTR
 945                      : two_d ? rot(to=reference, from=anchor,p=align)
 946                      : apply(zrot(-factor*spin)*frame_map(x=reference, z=BACK)*frame_map(x=factor*anchor, z=startdir, reverse=true),
 947                              align);
 948            spinaxis = two_d? UP : anchor_dir;
 949            olap = - overlap * reference - inset*inset_dir + shiftout * (inset_dir + factor*reference);
 950            if (norot || (approx(anchor_dir,reference) && anchor_spin==0)) {
 951                translate(pos) rot(v=spinaxis,a=factor*spin) translate(olap) default_tag("remove",inside) children();
 952            } else {
 953                translate(pos)
 954                    rot(v=spinaxis,a=factor*spin)
 955                    rot(anchor_spin,from=reference,to=anchor_dir){
 956                    translate(olap)
 957                        default_tag("remove",inside) children();}}
 958        }
 959    }
 960}
 961
 962
 963// Section: Tagging
 964
 965// Module: tag()
 966// Synopsis: Assigns a tag to an object
 967// Topics: Attachments
 968// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect()
 969// Usage:
 970//   PARENT() tag(tag) CHILDREN;
 971// Description:
 972//   Assigns the specified tag to all of the children. Note that if you want
 973//   to apply a tag to non-tag-aware objects you need to use {{force_tag()}} instead.
 974//   This works by setting the `$tag` variable, but it provides extra error checking and
 975//   handling of scopes.  You may set `$tag` directly yourself, but this is not recommended.
 976//   .
 977//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 978// Arguments:
 979//   tag = tag string, which must not contain any spaces.
 980// Side Effects:
 981//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
 982// Example(3D):  Applies the tag to both cuboids instead of having to repeat `$tag="remove"` for each one.
 983//   diff("remove")
 984//     cuboid(10){
 985//       position(TOP) cuboid(3);
 986//       tag("remove")
 987//       {
 988//         position(FRONT) cuboid(3);
 989//         position(RIGHT) cuboid(3);
 990//       }
 991//     }
 992module tag(tag)
 993{
 994    req_children($children);
 995    check=
 996      assert(is_string(tag),"tag must be a string")
 997      assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
 998    $tag = str($tag_prefix,tag);
 999    children();
1000}
1001
1002
1003// Module: force_tag()
1004// Synopsis: Assigns a tag to a non-attachable object.
1005// Topics: Attachments
1006// See Also: tag(), recolor(), hide(), show_only(), diff(), intersect()
1007// Usage:
1008//   PARENT() force_tag([tag]) CHILDREN;
1009// Description:
1010//   You use this module when you want to make a non-attachable or non-BOSL2 module respect tags.
1011//   It applies to its children the tag specified (or the tag currently in force if you don't specify a tag),
1012//   making a final determination about whether to show or hide the children.
1013//   This means that tagging in children's children will be ignored.
1014//   This module is specifically provided for operating on children that are not tag aware such as modules
1015//   that don't use {{attachable()}} or built in modules such as
1016//   - `polygon()`
1017//   - `projection()`
1018//   - `polyhedron()`  (or use [`vnf_polyhedron()`](vnf.scad#vnf_polyhedron))
1019//   - `linear_extrude()`  (or use [`linear_sweep()`](regions.scad#linear_sweep))
1020//   - `rotate_extrude()`
1021//   - `surface()`
1022//   - `import()`
1023//   - `difference()`
1024//   - `intersection()`
1025//   - `hull()`
1026//   .
1027//   When you use tag-based modules like {{diff()}} with a non-attachable module, the result may be puzzling.
1028//   Any time a test occurs for display of child() that test will succeed.  This means that when diff() checks
1029//   to see if it should show a module it will show it, and when diff() checks to see if it should subtract the module
1030//   it will subtract it.  The result will be a hole, possibly with zero-thickness edges or faces.  In order to
1031//   get the correct behavior, every non-attachable module needs an invocation of force_tag, even ones
1032//   that are not tagged.
1033//   .
1034//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1035// Arguments:
1036//   tag = tag string, which must not contain any spaces
1037// Side Effects:
1038//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
1039// Example(2D): This example produces the full square without subtracting the "remove" item.  When you use non-attachable modules with tags, results are unpredictable.
1040//   diff()
1041//   {
1042//     polygon(square(10));
1043//     move(-[.01,.01])polygon(square(5),$tag="remove");
1044//   }
1045// Example(2D): Adding force_tag() fixes the model.  Note you need to add it to *every* non-attachable module, even the untagged ones, as shown here.
1046//   diff()
1047//   {
1048//     force_tag()
1049//       polygon(square(10));
1050//     force_tag("remove")
1051//       move(-[.01,.01])polygon(square(5));
1052//   }
1053module force_tag(tag)
1054{
1055    req_children($children);
1056    check1=assert(is_undef(tag) || is_string(tag),"tag must be a string");
1057    $tag = str($tag_prefix,default(tag,$tag));
1058    assert(undef==str_find($tag," "),str("Tag string \"",$tag,"\" contains a space, which is not allowed"));
1059    if(_is_shown())
1060      show_all()
1061        children();
1062}
1063
1064
1065
1066// Module: default_tag()
1067// Synopsis: Sets a default tag for all children.
1068// Topics: Attachments
1069// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect()
1070// Usage:
1071//   PARENT() default_tag(tag) CHILDREN;
1072// Description:
1073//   Sets a default tag for all of the children.  This is intended to be used to set a tag for a whole module
1074//   that is then used outside the module, such as setting the tag to "remove" for easy operation with {{diff()}}.
1075//   The default_tag() module sets the `$tag` variable only if it is not already
1076//   set so you can have a module set a default tag of "remove" but that tag can be overridden by a {{tag()}}
1077//   in force from a parent.  If you use {{tag()}} it will override any previously
1078//   specified tag from a parent, which can be very confusing to a user trying to change the tag on a module.
1079//   The `do_tag` parameter allows you to apply a default tag conditionally without having to repeat the children.  
1080//   .
1081//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1082// Arguments:
1083//   tag = tag string, which must not contain any spaces.
1084//   do_tag = if false do not set the tag.  
1085// Side Effects:
1086//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
1087// Example(3D):  The module thing() is defined with {{tag()}} and the user applied tag of "keep_it" is ignored, leaving the user puzzled.
1088//   module thing() { tag("remove") cuboid(10);}
1089//   diff()
1090//     cuboid(20){
1091//       position(TOP) thing();
1092//       position(RIGHT) tag("keep_it") thing();
1093//   }
1094// Example(3D):  Using default_tag() fixes this problem: the user applied tag does not get overridden by the tag hidden in the module definition.
1095//   module thing() { default_tag("remove") cuboid(10);}
1096//   diff()
1097//     cuboid(20){
1098//       position(TOP) thing();
1099//       position(RIGHT) tag("keep_it") thing();
1100//   }
1101module default_tag(tag,do_tag=true)
1102{
1103    if ($tag=="" && do_tag) tag(tag) children();
1104    else children();
1105}
1106
1107
1108// Module: tag_scope()
1109// Synopsis: Creates a new tag scope.
1110// See Also: tag(), force_tag(), default_tag()
1111// Topics: Attachments
1112// Usage:
1113//   tag_scope([scope]) CHILDREN;
1114// Description:
1115//   Creates a tag scope with locally altered tag names to avoid tag name conflict with other code.
1116//   This is necessary when writing modules because the module's caller might happen to use the same tags.
1117//   Note that if you directly set the `$tag` variable then tag scoping will not work correctly.
1118// Side Effects:
1119//   `$tag_prefix` is set to the value of `scope=` if given, otherwise is set to a random string.
1120// Example: In this example the ring module uses "remove" tags which will conflict with use of the same tags by the parent.
1121//   module ring(r,h,w=1,anchor,spin,orient)
1122//   {
1123//     tag_scope("ringscope")
1124//       attachable(anchor,spin,orient,r=r,h=h){
1125//         diff()
1126//           cyl(r=r,h=h)
1127//             tag("remove") cyl(r=r-w,h=h+1);
1128//         children();
1129//       }
1130//   }
1131//   // Calling the module using "remove" tags
1132//   // will conflict with internal tag use in
1133//   // the ring module.
1134//   $fn=32;
1135//   diff(){
1136//       ring(10,7,w=4);
1137//       tag("remove")ring(8,8);
1138//       tag("remove")diff("rem"){
1139//          ring(9.5,8,w=1);
1140//          tag("rem")ring(9.5,8,w=.3);
1141//       }
1142//     }
1143module tag_scope(scope){
1144  req_children($children);
1145  scope = is_undef(scope) ? rand_str(20) : scope;
1146  assert(is_string(scope), "scope must be a string");
1147  assert(undef==str_find(scope," "),str("Scope string \"",scope,"\" contains a space, which is not allowed"));
1148  $tag_prefix=scope;
1149  children();
1150}
1151
1152
1153// Section: Attachment Modifiers
1154
1155// Module: diff()
1156// Synopsis: Performs a differencing operation using tags rather than hierarchy to control what happens.
1157// Topics: Attachments
1158// See Also: tag(), force_tag(), recolor(), show_only(), hide(), tag_diff(), intersect(), tag_intersect()
1159// Usage:
1160//   diff([remove], [keep]) PARENT() CHILDREN;
1161// Description:
1162//   Performs a differencing operation using tags to control what happens.  This is specifically intended to
1163//   address the situation where you want differences between a parent and child object, something
1164//   that is impossible with the native difference() module.
1165//   The children to diff are grouped into three categories, regardless of nesting level.
1166//   The `remove` argument is a space delimited list of tags specifying objects to
1167//   subtract.  The `keep` argument is a similar list of tags giving objects to be kept.
1168//   Objects not matching either the `remove` or `keep` lists form the third category of base objects.
1169//   To produce its output, diff() forms the union of all the base objects and then
1170//   subtracts all the objects with tags in `remove`.  Finally it adds in objects listed in `keep`.
1171//   Attachable objects should be tagged using {{tag()}}
1172//   and non-attachable objects with {{force_tag()}}.
1173//   .
1174//   Remember when using tagged operations with that the operations don't happen in hierarchical order, since
1175//   the point of tags is to break the hierarchy.  If you tag an object with a keep tag, nothing will be
1176//   subtracted from it, no matter where it appears because kept objects are unioned in at the end.
1177//   If you want a child of an object tagged with a remove tag to stay in the model it may be
1178//   better to give it a tag that is not a remove tag or a keep tag.  Such an object *will* be subject to
1179//   subtractions from other remove-tagged objects.
1180//   .
1181//   Note that `diff()` invokes its children three times.
1182//   .
1183//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1184// Arguments:
1185//   remove = String containing space delimited set of tag names of children to difference away.  Default: `"remove"`
1186//   keep = String containing space delimited set of tag names of children to keep; that is, to union into the model after differencing is completed.  Default: `"keep"`
1187// Example: Diffing using default tags
1188//   diff()
1189//   cuboid(50) {
1190//       tag("remove") attach(TOP) sphere(d=40);
1191//       tag("keep") attach(CTR) cylinder(h=40, d=10);
1192//   }
1193// Example: The "hole" items are subtracted from everything else.  The other tags can be anything you find convenient.
1194//   diff("hole")
1195//     tag("body")sphere(d=100) {
1196//       tag("pole") zcyl(d=55, h=100);  // attach() not needed for center-to-center.
1197//       tag("hole") {
1198//          xcyl(d=55, h=101);
1199//          ycyl(d=55, h=101);
1200//       }
1201//       tag("axle")zcyl(d=15, h=140);
1202//     }
1203// Example:
1204//   diff(keep="axle")
1205//   sphere(d=100) {
1206//       tag("axle")xcyl(d=40, l=120);
1207//       tag("remove")cuboid([40,120,100]);
1208//   }
1209// Example: Masking
1210//   diff()
1211//   cube([80,90,100], center=true) {
1212//       edge_mask(FWD)
1213//           rounding_edge_mask(l=max($parent_size)*1.01, r=25);
1214//   }
1215// Example: Here we subtract the parent object from the child.  Because tags propagate to children we need to clear the "remove" tag from the child.
1216//  diff()
1217//     tag("remove")cuboid(10)
1218//       tag("")position(RIGHT+BACK)cyl(r=8,h=9);
1219// Example(3D,VPR=[104,0,200], VPT=[-0.9,3.03, -0.74], VPD=19,NoAxes,NoScales): A pipe module that subtracts its interior when you call it using diff().  Normally if you union two pipes together, you'll get interfering walls at the intersection, but not here:
1220//   $fn=16;
1221//   // This module must be called by subtracting with "diff"
1222//   module pipe(length, od, id) {
1223//       // Strip the tag the user is using to subtract
1224//       tag("")cylinder(h=length, d=od, center=true);
1225//       // Leave the tag alone here, so this one is removed
1226//       cylinder(h=length+.02, d=id, center=true);
1227//   }
1228//   // Draw some intersecting pipes
1229//   diff(){
1230//     tag("remove"){
1231//       pipe(length=5, od=2, id=1.9);
1232//       zrot(10)xrot(75)
1233//         pipe(length=5, od=2, id=1.9);
1234//     }
1235//     // The orange bar has its center removed
1236//     color("orange") down(1) xcyl(h=8, d=1);
1237//     // "keep" preserves the interior of the blue bar intact
1238//     tag("keep") recolor("blue") up(1) xcyl(h=8, d=1);
1239//   }
1240//   // Objects outside the diff don't have pipe interiors removed
1241//   color("purple") down(2.2) ycyl(h=8, d=0.3);
1242// Example(3D,NoScales,NoAxes): Nested diff() calls work as expected, but be careful of reusing tag names, even hidden in submodules.
1243//   $fn=32;
1244//   diff("rem1")
1245//   cyl(r=10,h=10){
1246//     diff("rem2",$tag="rem1"){
1247//       cyl(r=8,h=11);
1248//       tag("rem2")diff("rem3"){
1249//           cyl(r=6,h=12);
1250//           tag("rem3")cyl(r=4,h=13);
1251//           }
1252//       }
1253//   }
1254// Example: This example shows deep nesting, where all the differences cross levels.  Unlike the preceding example, each cylinder is positioned relative to its parent.  Note that it suffices to use two remove tags, alternating between them at each level.
1255//   $fn=32;
1256//   diff("remA")
1257//     cyl(r=9, h=6)
1258//       tag("remA")diff("remB")
1259//         left(.2)position(RIGHT)cyl(r=8,h=7,anchor=RIGHT)
1260//           tag("remB")diff("remA")
1261//            left(.2)position(LEFT)cyl(r=7,h=7,anchor=LEFT)
1262//              tag("remA")diff("remB")
1263//                left(.2)position(LEFT)cyl(r=6,h=8,anchor=LEFT)
1264//                  tag("remB")diff("remA")
1265//                    right(.2)position(RIGHT)cyl(r=5,h=9,anchor=RIGHT)
1266//                      tag("remA")diff("remB")
1267//                        right(.2)position(RIGHT)cyl(r=4,h=10,anchor=RIGHT)
1268//                          tag("remB")left(.2)position(LEFT)cyl(r=3,h=11,anchor=LEFT);
1269// Example(3D,NoAxes,NoScales): When working with Non-Attachables like rotate_extrude() you must apply {{force_tag()}} to every non-attachable object.
1270//   back_half()
1271//     diff("remove")
1272//       cuboid(40) {
1273//         attach(TOP)
1274//           recolor("lightgreen")
1275//             cyl(l=10,d=30);
1276//         position(TOP+RIGHT)
1277//           force_tag("remove")
1278//             xrot(90)
1279//               rotate_extrude()
1280//                 right(20)
1281//                   circle(5);
1282//       }
1283// Example: Here is another example where two children are intersected using the native intersection operator, and then tagged with {{force_tag()}}.  Note that because the children are at the same level, you don't need to use a tagged operator for their intersection.
1284//  $fn=32;
1285//  diff()
1286//    cuboid(10){
1287//      force_tag("remove")intersection()
1288//        {
1289//          position(RIGHT) cyl(r=7,h=15);
1290//          position(LEFT) cyl(r=7,h=15);
1291//        }
1292//      tag("keep")cyl(r=1,h=9);
1293//    }
1294// Example: In this example the children that are subtracted are each at different nesting levels, with a kept object in between.
1295//   $fn=32;
1296//   diff()
1297//     cuboid(10){
1298//       tag("remove")cyl(r=4,h=11)
1299//         tag("keep")cyl(r=3,h=17)
1300//           tag("remove")position(RIGHT)cyl(r=2,h=18);
1301//     }
1302// Example: Combining tag operators can be tricky.  Here the `diff()` operation keeps two tags, "fullkeep" and "keep".  Then {{intersect()}} intersects the "keep" tagged item with everything else, but keeps the "fullkeep" object.
1303//   $fn=32;
1304//   intersect("keep","fullkeep")
1305//     diff(keep="fullkeep keep")
1306//       cuboid(10){
1307//         tag("remove")cyl(r=4,h=11);
1308//         tag("keep") position(RIGHT)cyl(r=8,h=12);
1309//         tag("fullkeep")cyl(r=1,h=12);
1310//     }
1311// Example: In this complex example we form an intersection, subtract an object, and keep some objects.  Note that for the small cylinders on either side, marking them as "keep" or removing their tag gives the same effect.  This is because without a tag they become part of the intersection and the result ends up the same.  For the two cylinders at the back, however, the result is different.  With "keep" the cylinder on the left appears whole, but without it, the cylinder at the back right is subject to intersection.
1312//   $fn=64;
1313//   diff()
1314//     intersect(keep="remove keep")
1315//       cuboid(10,$thing="cube"){
1316//         tag("intersect"){
1317//           position(RIGHT) cyl(r=5.5,h=15)
1318//              tag("")cyl(r=2,h=10);
1319//           position(LEFT) cyl(r=5.54,h=15)
1320//              tag("keep")cyl(r=2,h=10);
1321//         }
1322//         // Untagged it is in the intersection
1323//         tag("") position(BACK+RIGHT)
1324//           cyl(r=2,h=10,anchor=CTR);
1325//         // With keep the full cylinder appears
1326//         tag("keep") position(BACK+LEFT)
1327//           cyl(r=2,h=10,anchor=CTR);
1328//         tag("remove") cyl(r=3,h=15);
1329//       }
1330module diff(remove="remove", keep="keep")
1331{
1332    req_children($children);
1333    assert(is_string(remove),"remove must be a string of tags");
1334    assert(is_string(keep),"keep must be a string of tags");
1335    if (_is_shown())
1336    {
1337        difference() {
1338            hide(str(remove," ",keep)) children();
1339            show_only(remove) children();
1340        }
1341    }
1342    show_int(keep)children();
1343}
1344
1345
1346// Module: tag_diff()
1347// Synopsis: Performs a {{diff()}} and then sets a tag on the result.
1348// Topics: Attachments
1349// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), intersect(), tag_intersect()
1350// Usage:
1351//   tag_diff(tag, [remove], [keep]) PARENT() CHILDREN;
1352// Description:
1353//   Perform a differencing operation in the manner of {{diff()}} using tags to control what happens,
1354//   and then tag the resulting difference object with the specified tag.  This forces the specified
1355//   tag to be resolved at the level of the difference operation.  In most cases, this is not necessary,
1356//   but if you have kept objects and want to operate on this difference object as a whole object using
1357//   more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
1358//   .
1359//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1360// Arguments:
1361//   tag = Tag string to apply to this difference object
1362//   remove = String containing space delimited set of tag names of children to difference away.  Default: `"remove"`
1363//   keep = String containing space delimited set of tag names of children to keep; that is, to union into the model after differencing is completed.  Default: `"keep"`
1364// Side Effects:
1365//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
1366// Example: In this example we have a difference with a kept object that is then subtracted from a cube, but we don't want the kept object to appear in the final output, so this result is wrong:
1367//   diff("rem"){
1368//     cuboid([20,10,30],anchor=FRONT);
1369//     tag("rem")diff("remove","keep"){
1370//       cuboid([10,10,20]);
1371//       tag("remove")cuboid([11,11,5]);
1372//       tag("keep")cuboid([2,2,20]);
1373//     }
1374//   }
1375// Example: Using tag_diff corrects the problem:
1376//   diff("rem"){
1377//     cuboid([20,10,30],anchor=FRONT);
1378//       tag_diff("rem","remove","keep"){
1379//         cuboid([10,10,20]);
1380//         tag("remove")cuboid([11,11,5]);
1381//         tag("keep")cuboid([2,2,20]);
1382//       }
1383//   }
1384// Example: This concentric cylinder example uses "keep" and produces the wrong result.  The kept cylinder gets kept in the final output instead of subtracted.  This happens even when we make sure to change the `keep` argument at the top level {{diff()}} call.
1385//   diff("rem","nothing")
1386//     cyl(r=8,h=6)
1387//       tag("rem")diff()
1388//         cyl(r=7,h=7)
1389//           tag("remove")cyl(r=6,h=8)
1390//           tag("keep")cyl(r=5,h=9);
1391// Example: Changing to tag_diff() causes the kept cylinder to be subtracted, producing the desired result:
1392//   diff("rem")
1393//     cyl(r=8,h=6)
1394//       tag_diff("rem")
1395//         cyl(r=7,h=7)
1396//           tag("remove")cyl(r=6,h=8)
1397//           tag("keep")cyl(r=5,h=9);
1398module tag_diff(tag,remove="remove", keep="keep")
1399{
1400    req_children($children);
1401    assert(is_string(remove),"remove must be a string of tags");
1402    assert(is_string(keep),"keep must be a string of tags");
1403    assert(is_string(tag),"tag must be a string");
1404    assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
1405    $tag=str($tag_prefix,tag);
1406    if (_is_shown())
1407      show_all(){
1408         difference() {
1409            hide(str(remove," ",keep)) children();
1410            show_only(remove) children();
1411         }
1412         show_only(keep)children();
1413      }
1414}
1415
1416
1417// Module: intersect()
1418// Synopsis: Perform an intersection operation on children using tags rather than hierarchy to control what happens.
1419// Topics: Attachments
1420// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), tag_diff(), tag_intersect()
1421// Usage:
1422//   intersect([intersect], [keep]) PARENT() CHILDREN;
1423// Description:
1424//   Performs an intersection operation on its children, using tags to
1425//   determine what happens.  This is specifically intended to address
1426//   the situation where you want intersections involving a parent and
1427//   child object, something that is impossible with the native
1428//   intersection() module.  This module treats the children in three
1429//   groups: objects matching the tags listed in `intersect`, objects
1430//   matching tags listed in `keep`, and the remaining objects that
1431//   don't match any of the listed tags.  The intersection is computed
1432//   between the union of the `intersect` tagged objects and union of the objects that don't
1433//   match any of the listed tags.  Finally the objects listed in `keep` are
1434//   unioned with the result.  Attachable objects should be tagged using {{tag()}}
1435//   and non-attachable objects with {{force_tag()}}.
1436//   .
1437//   Note that `intersect()` invokes its children three times.
1438//   .
1439//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1440// Arguments:
1441//   intersect = String containing space delimited set of tag names of children to intersect.  Default: "intersect"
1442//   keep = String containing space delimited set of tag names of children to keep whole.  Default: "keep"
1443// Example:
1444//   intersect("mask", keep="axle")
1445//     sphere(d=100) {
1446//         tag("mask")cuboid([40,100,100]);
1447//         tag("axle")xcyl(d=40, l=100);
1448//     }
1449// Example: Combining tag operators can be tricky.  Here the {{diff()}} operation keeps two tags, "fullkeep" and "keep".  Then `intersect()` intersects the "keep" tagged item with everything else, but keeps the "fullkeep" object.
1450//   $fn=32;
1451//   intersect("keep","fullkeep")
1452//     diff(keep="fullkeep keep")
1453//       cuboid(10){
1454//         tag("remove")cyl(r=4,h=11);
1455//         tag("keep") position(RIGHT)cyl(r=8,h=12);
1456//         tag("fullkeep")cyl(r=1,h=12);
1457//     }
1458// Example: In this complex example we form an intersection, subtract an object, and keep some objects.  Note that for the small cylinders on either side, marking them as "keep" or removing their tag gives the same effect.  This is because without a tag they become part of the intersection and the result ends up the same.  For the two cylinders at the back, however, the result is different.  With "keep" the cylinder on the left appears whole, but without it, the cylinder at the back right is subject to intersection.
1459//   $fn=64;
1460//   diff()
1461//     intersect(keep="remove keep")
1462//       cuboid(10,$thing="cube"){
1463//         tag("intersect"){
1464//           position(RIGHT) cyl(r=5.5,h=15)
1465//              tag("")cyl(r=2,h=10);
1466//           position(LEFT) cyl(r=5.54,h=15)
1467//              tag("keep")cyl(r=2,h=10);
1468//         }
1469//         // Untagged it is in the intersection
1470//         tag("") position(BACK+RIGHT)
1471//           cyl(r=2,h=10,anchor=CTR);
1472//         // With keep the full cylinder appears
1473//         tag("keep") position(BACK+LEFT)
1474//           cyl(r=2,h=10,anchor=CTR);
1475//         tag("remove") cyl(r=3,h=15);
1476//       }
1477module intersect(intersect="intersect",keep="keep")
1478{
1479   assert(is_string(intersect),"intersect must be a string of tags");
1480   assert(is_string(keep),"keep must be a string of tags");
1481   intersection(){
1482      show_only(intersect) children();
1483      hide(str(intersect," ",keep)) children();
1484   }
1485   show_int(keep) children();
1486}
1487
1488
1489// Module: tag_intersect()
1490// Synopsis: Performs an {{intersect()}} and then tags the result.
1491// Topics: Attachments
1492// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), tag_diff(), intersect()
1493// Usage:
1494//   tag_intersect(tag, [intersect], [keep]) PARENT() CHILDREN;
1495// Description:
1496//   Perform an intersection operation in the manner of {{intersect()}} using tags to control what happens,
1497//   and then tag the resulting difference object with the specified tag.  This forces the specified
1498//   tag to be resolved at the level of the intersect operation.  In most cases, this is not necessary,
1499//   but if you have kept objects and want to operate on this difference object as a whole object using
1500//   more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
1501//   .
1502//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1503// Arguments:
1504//   tag = Tag to set for the intersection
1505//   intersect = String containing space delimited set of tag names of children to intersect.  Default: "intersect"
1506//   keep = String containing space delimited set of tag names of children to keep whole.  Default: "keep"
1507// Side Effects:
1508//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
1509// Example:  Without `tag_intersect()` the kept object is not included in the difference.
1510//   $fn=32;
1511//   diff()
1512//     cuboid([20,15,9])
1513//     tag("remove")intersect()
1514//       cuboid(10){
1515//         tag("intersect")position(RIGHT) cyl(r=7,h=10);
1516//         tag("keep")position(LEFT)cyl(r=4,h=10);
1517//       }
1518// Example: Using tag_intersect corrects the problem.
1519//   $fn=32;
1520//   diff()
1521//     cuboid([20,15,9])
1522//     tag_intersect("remove")
1523//       cuboid(10){
1524//         tag("intersect")position(RIGHT) cyl(r=7,h=10);
1525//         tag("keep")position(LEFT)cyl(r=4,h=10);
1526//       }
1527module tag_intersect(tag,intersect="intersect",keep="keep")
1528{
1529   assert(is_string(intersect),"intersect must be a string of tags");
1530   assert(is_string(keep),"keep must be a string of tags");
1531   assert(is_string(tag),"tag must be a string");
1532   assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
1533   $tag=str($tag_prefix,tag);
1534   if (_is_shown())
1535     show_all(){
1536       intersection(){
1537          show_only(intersect) children();
1538          hide(str(intersect," ",keep)) children();
1539       }
1540       show_only(keep) children();
1541   }
1542}
1543
1544
1545// Module: conv_hull()
1546// Synopsis:  Performs a hull operation on the children using tags to determine what happens.
1547// Topics: Attachments, Hulling
1548// See Also: tag(), recolor(), show_only(), hide(), diff(), intersect(), hull()
1549// Usage:
1550//   conv_hull([keep]) CHILDREN;
1551// Description:
1552//   Performs a hull operation on the children using tags to determine what happens.  The items
1553//   not tagged with the `keep` tags are combined into a convex hull, and the children tagged with the keep tags
1554//   are unioned with the result.
1555//   .
1556//   Note that `conv_hull()` invokes its children twice.  
1557//   .
1558//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1559// Arguments:
1560//   keep = String containing space delimited set of tag names of children to keep out of the hull.  Default: "keep"
1561// Example:
1562//   conv_hull("keep")
1563//      sphere(d=100, $fn=64) {
1564//        cuboid([40,90,90]);
1565//        tag("keep")xcyl(d=40, l=120);
1566//      }
1567// Example: difference combined with hull where all objects are relative to each other.
1568//   $fn=32;
1569//   diff()
1570//     conv_hull("remove")
1571//       cuboid(10)
1572//         position(RIGHT+BACK)cyl(r=4,h=10)
1573//           tag("remove")cyl(r=2,h=12);
1574module conv_hull(keep="keep")
1575{
1576    req_children($children);
1577    assert(is_string(keep),"keep must be a string of tags");
1578    if (_is_shown())
1579        hull() hide(keep) children();
1580    show_int(keep) children();
1581}
1582
1583
1584// Module: tag_conv_hull()
1585// Synopsis: Performs a {{conv_hull()}} and then sets a tag on the result.
1586// Topics: Attachments
1587// See Also: tag(), recolor(), show_only(), hide(), diff(), intersect()
1588// Usage:
1589//   tag_conv_hull(tag, [keep]) CHILDREN;
1590// Description:
1591//   Perform a convex hull operation in the manner of {{conv_hull()}} using tags to control what happens,
1592//   and then tag the resulting hull object with the specified tag.  This forces the specified
1593//   tag to be resolved at the level of the hull operation.  In most cases, this is not necessary,
1594//   but if you have kept objects and want to operate on the hull object as a whole object using
1595//   more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
1596//   .
1597//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1598// Arguments:
1599//   keep = String containing space delimited set of tag names of children to keep out of the hull.  Default: "keep"
1600// Side Effects:
1601//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
1602// Example: With a regular tag, the kept object is not handled as desired:
1603//   diff(){
1604//      cuboid([30,30,9])
1605//        tag("remove")conv_hull("remove")
1606//          cuboid(10,anchor=LEFT+FRONT){
1607//            position(RIGHT+BACK)cyl(r=4,h=10);
1608//            tag("keep")position(FRONT+LEFT)cyl(r=4,h=10);
1609//          }
1610//   }
1611// Example: Using `tag_conv_hull()` fixes the problem:
1612//   diff(){
1613//      cuboid([30,30,9])
1614//        tag_conv_hull("remove")
1615//          cuboid(10,anchor=LEFT+FRONT){
1616//            position(RIGHT+BACK)cyl(r=4,h=10);
1617//            tag("keep")position(FRONT+LEFT)cyl(r=4,h=10);
1618//          }
1619//   }
1620module tag_conv_hull(tag,keep="keep")
1621{
1622    req_children($children);
1623    assert(is_string(keep),"keep must be a string of tags");
1624    assert(is_string(tag),"tag must be a string");
1625    assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
1626    $tag=str($tag_prefix,tag);
1627    if (_is_shown())
1628      show_all(){
1629        hull() hide(keep) children();
1630        show_only(keep) children();
1631      }
1632}
1633
1634
1635// Module: hide()
1636// Synopsis: Hides attachable children with the given tags.
1637// Topics: Attachments
1638// See Also: tag(), recolor(), show_only(), show_all(), show_int(), diff(), intersect()
1639// Usage:
1640//   hide(tags) CHILDREN;
1641// Description:
1642//   Hides all attachable children with the given tags, which you supply as a space separated string. Previously hidden objects remain hidden, so hiding is cumulative, unlike `show_only()`.
1643//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1644// Side Effects:
1645//   Sets `$tags_hidden` to include the tags you specify.
1646// Example:  Hides part of the model.
1647//   hide("A")
1648//     tag("main") cube(50, anchor=CENTER, $tag="Main") {
1649//       tag("A")attach(LEFT, BOTTOM) cylinder(d=30, h=30);
1650//       tag("B")attach(RIGHT, BOTTOM) cylinder(d=30, h=30);
1651//     }
1652// Example: Use an invisible parent to position children.  Note that children must be retagged because they inherit the parent tag.
1653//   $fn=16;
1654//   hide("hidden")
1655//     tag("hidden")cuboid(10)
1656//       tag("visible") {
1657//         position(RIGHT) cyl(r=1,h=12);
1658//         position(LEFT) cyl(r=1,h=12);
1659//       }
1660module hide(tags)
1661{
1662    req_children($children);
1663    dummy=assert(is_string(tags), "tags must be a string");
1664    taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1665    $tags_hidden = concat($tags_hidden,taglist);
1666    children();
1667}
1668
1669
1670// Module: show_only()
1671// Synopsis: Show only the children with the listed tags.
1672// See Also: tag(), recolor(), show_all(), show_int(), diff(), intersect()
1673// Topics: Attachments
1674// Usage:
1675//   show_only(tags) CHILDREN;
1676// Description:
1677//   Show only the children with the listed tags, which you sply as a space separated string.  Only unhidden objects will be shown, so if an object is hidden either before or after the `show_only()` call then it will remain hidden.  This overrides any previous `show_only()` calls.  Unlike `hide()`, calls to `show_only()` are not cumulative.
1678//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1679// Side Effects:
1680//   Sets `$tags_shown` to the tag you specify.
1681// Example:  Display the attachments but not the parent
1682//   show_only("visible")
1683//     cube(50, anchor=CENTER)
1684//       tag("visible"){
1685//         attach(LEFT, BOTTOM) cylinder(d=30, h=30);
1686//         attach(RIGHT, BOTTOM) cylinder(d=30, h=30);
1687//       }
1688module show_only(tags)
1689{
1690    req_children($children);
1691    dummy=assert(is_string(tags), str("tags must be a string",tags));
1692    taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1693    $tags_shown = taglist;
1694    children();
1695}
1696
1697// Module: show_all()
1698// Synopsis: Shows all children and clears tags.
1699// See Also: tag(), recolor(), show_only(), show_int(), diff(), intersect()
1700// Topics: Attachments
1701// Usage;
1702//   show_all() CHILDREN;
1703// Description:
1704//   Shows all children.  Clears the list of hidden tags and shown tags so that all child objects will be
1705//   fully displayed.
1706// Side Effects:
1707//   Sets `$tags_shown="ALL"`
1708//   Sets `$tags_hidden=[]`
1709module show_all()
1710{
1711   req_children($children);
1712   $tags_shown="ALL";
1713   $tags_hidden=[];
1714   children();
1715}
1716
1717
1718// Module: show_int()
1719// Synopsis: Shows children with the listed tags which were already shown in the parent context.
1720// See Also: tag(), recolor(), show_only(), show_all(), show_int(), diff(), intersect()
1721// Topics: Attachments
1722// Usage:
1723//   show_int(tags) CHILDREN;
1724// Description:
1725//   Show only the children with the listed tags which were already shown in the parent context.
1726//   This intersects the current show list with the list of tags you provide.
1727// Arguments:
1728//   tags = list of tags to show
1729// Side Effects:
1730//   Sets `$tags_shown`
1731module show_int(tags)
1732{
1733    req_children($children);
1734    dummy=assert(is_string(tags), str("tags must be a string",tags));
1735    taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1736    $tags_shown = $tags_shown == "ALL" ? taglist : set_intersection($tags_shown,taglist);
1737    children();
1738}
1739
1740
1741// Section: Mask Attachment
1742
1743
1744// Module: face_mask()
1745// Synopsis: Ataches a 3d mask shape to the given faces of the parent.
1746// SynTags: Trans
1747// Topics: Attachments, Masking
1748// See Also: attachable(), position(), attach(), edge_mask(), corner_mask(), face_profile(), edge_profile(), corner_profile()
1749// Usage:
1750//   PARENT() face_mask(faces) CHILDREN;
1751// Description:
1752//   Takes a 3D mask shape, and attaches it to the given faces, with the appropriate orientation to be
1753//   differenced away.  The mask shape should be vertically oriented (Z-aligned) with the bottom half
1754//   (Z-) shaped to be diffed away from the face of parent attachable shape.  If no tag is set then
1755//   `face_mask()` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1756//   For details on specifying the faces to mask see [Specifying Faces](attachments.scad#subsection-specifying-faces).
1757//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1758// Arguments:
1759//   edges = Faces to mask.  See  [Specifying Faces](attachments.scad#subsection-specifying-faces) for information on specifying faces.  Default: All faces
1760// Side Effects:
1761//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1762//   `$idx` is set to the index number of each face in the list of faces given.
1763//   `$attach_anchor` is set for each face given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1764// Example:
1765//   diff()
1766//   cylinder(r=30, h=60)
1767//       face_mask(TOP) {
1768//           rounding_cylinder_mask(r=30,rounding=5);
1769//           cuboid([5,61,10]);
1770//       }
1771// Example: Using `$idx`
1772//   diff()
1773//   cylinder(r=30, h=60)
1774//       face_mask([TOP, BOT])
1775//           zrot(45*$idx) zrot_copies([0,90]) cuboid([5,61,10]);
1776module face_mask(faces=[LEFT,RIGHT,FRONT,BACK,BOT,TOP]) {
1777    req_children($children);
1778    faces = is_vector(faces)? [faces] : faces;
1779    assert(all([for (face=faces) is_vector(face) && sum([for (x=face) x!=0? 1 : 0])==1]), "Vector in faces doesn't point at a face.");
1780    assert($parent_geom != undef, "No object to attach to!");
1781    attach(faces) {
1782       default_tag("remove") children();
1783    }
1784}
1785
1786
1787// Module: edge_mask()
1788// Synopsis: Attaches a 3D mask shape to the given edges of the parent.
1789// SynTags: Trans
1790// Topics: Attachments, Masking
1791// See Also: attachable(), position(), attach(), face_mask(), corner_mask(), face_profile(), edge_profile(), corner_profile()
1792// Usage:
1793//   PARENT() edge_mask([edges], [except]) CHILDREN;
1794// Description:
1795//   Takes a 3D mask shape, and attaches it to the given edges, with the appropriate orientation to be
1796//   differenced away.  The mask shape should be vertically oriented (Z-aligned) with the back-right
1797//   quadrant (X+Y+) shaped to be diffed away from the edge of parent attachable shape.  If no tag is set
1798//   then `edge_mask` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1799//   For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges).
1800//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1801// Figure: A Typical Edge Rounding Mask
1802//   module roundit(l,r) difference() {
1803//       translate([-1,-1,-l/2])
1804//           cube([r+1,r+1,l]);
1805//       translate([r,r])
1806//           cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
1807//   }
1808//   roundit(l=30,r=10);
1809// Arguments:
1810//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: All edges.
1811//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: No edges.
1812// Side Effects:
1813//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1814//   `$idx` is set to the index number of each edge.
1815//   `$attach_anchor` is set for each edge given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1816//   `$parent_size` is set to the size of the parent object.
1817// Example:
1818//   diff()
1819//   cube([50,60,70],center=true)
1820//       edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT])
1821//           rounding_edge_mask(l=71,r=10);
1822module edge_mask(edges=EDGES_ALL, except=[]) {
1823    req_children($children);
1824    assert($parent_geom != undef, "No object to attach to!");
1825    edges = _edges(edges, except=except);
1826    vecs = [
1827        for (i = [0:3], axis=[0:2])
1828        if (edges[axis][i]>0)
1829        EDGE_OFFSETS[axis][i]
1830    ];
1831    for ($idx = idx(vecs)) {
1832        vec = vecs[$idx];
1833        vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
1834        dummy=assert(vcount == 2, "Not an edge vector!");
1835        anch = _find_anchor(vec, $parent_geom);
1836        $attach_to = undef;
1837        $attach_anchor = anch;
1838        rotang =
1839            vec.z<0? [90,0,180+v_theta(vec)] :
1840            vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) :
1841            vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+v_theta(vec)] :
1842            [-90,0,180+v_theta(vec)];
1843        translate(anch[1]) rot(rotang)
1844           default_tag("remove") children();
1845    }
1846}
1847
1848
1849// Module: corner_mask()
1850// Synopsis: Attaches a 3d mask shape to the given corners of the parent.
1851// SynTags: Trans
1852// Topics: Attachments, Masking
1853// See Also: attachable(), position(), attach(), face_mask(), edge_mask(), face_profile(), edge_profile(), corner_profile()
1854// Usage:
1855//   PARENT() corner_mask([corners], [except]) CHILDREN;
1856// Description:
1857//   Takes a 3D mask shape, and attaches it to the specified corners, with the appropriate orientation to
1858//   be differenced away.  The 3D corner mask shape should be designed to mask away the X+Y+Z+ octant.  If no tag is set
1859//   then `corner_mask` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1860//   See [Specifying Corners](attachments.scad#subsection-specifying-corners) for information on how to specify corner sets.
1861//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1862// Arguments:
1863//   corners = Corners to mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: All corners.
1864//   except = Corners to explicitly NOT mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: No corners.
1865// Side Effects:
1866//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1867//   `$idx` is set to the index number of each corner.
1868//   `$attach_anchor` is set for each corner given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1869// Example:
1870//   diff()
1871//   cube(100, center=true)
1872//       corner_mask([TOP,FRONT],LEFT+FRONT+TOP)
1873//           difference() {
1874//               translate(-0.01*[1,1,1]) cube(20);
1875//               translate([20,20,20]) sphere(r=20);
1876//           }
1877module corner_mask(corners=CORNERS_ALL, except=[]) {
1878    req_children($children);
1879    assert($parent_geom != undef, "No object to attach to!");
1880    corners = _corners(corners, except=except);
1881    vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]];
1882    for ($idx = idx(vecs)) {
1883        vec = vecs[$idx];
1884        vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
1885        dummy=assert(vcount == 3, "Not an edge vector!");
1886        anch = _find_anchor(vec, $parent_geom);
1887        $attach_to = undef;
1888        $attach_anchor = anch;
1889        rotang = vec.z<0?
1890            [  0,0,180+v_theta(vec)-45] :
1891            [180,0,-90+v_theta(vec)-45];
1892        translate(anch[1]) rot(rotang)
1893            default_tag("remove") children();
1894    }
1895}
1896
1897
1898// Module: face_profile()
1899// Synopsis: Extrudes a 2D edge profile into a mask for all edges and corners of the given faces on the parent.
1900// SynTags: Geom
1901// Topics: Attachments, Masking
1902// See Also: attachable(), position(), attach(), edge_profile(), corner_profile(), face_mask(), edge_mask(), corner_mask()
1903// Usage:
1904//   PARENT() face_profile(faces, r|d=, [convexity=]) CHILDREN;
1905// Description:
1906//   Given a 2D edge profile, extrudes it into a mask for all edges and corners bounding each given face. If no tag is set
1907//   then `face_profile` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1908//   See  [Specifying Faces](attachments.scad#subsection-specifying-faces) for information on specifying faces.
1909//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1910// Arguments:
1911//   faces = Faces to mask edges and corners of.
1912//   r = Radius of corner mask.
1913//   ---
1914//   d = Diameter of corner mask.
1915//   excess = Excess length to extrude the profile to make edge masks.  Default: 0.01
1916//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
1917// Side Effects:
1918//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1919//   `$idx` is set to the index number of each face.
1920//   `$attach_anchor` is set for each edge or corner given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1921//   `$profile_type` is set to `"edge"` or `"corner"`, depending on what is being masked.
1922// Example:
1923//   diff()
1924//   cube([50,60,70],center=true)
1925//       face_profile(TOP,r=10)
1926//           mask2d_roundover(r=10);
1927module face_profile(faces=[], r, d, excess=0.01, convexity=10) {
1928    req_children($children);
1929    faces = is_vector(faces)? [faces] : faces;
1930    assert(all([for (face=faces) is_vector(face) && sum([for (x=face) x!=0? 1 : 0])==1]), "Vector in faces doesn't point at a face.");
1931    r = get_radius(r=r, d=d, dflt=undef);
1932    assert(is_num(r) && r>=0);
1933    edge_profile(faces, excess=excess) children();
1934    corner_profile(faces, convexity=convexity, r=r) children();
1935}
1936
1937
1938// Module: edge_profile()
1939// Synopsis: Extrudes a 2d edge profile into a mask on the given edges of the parent.
1940// SynTags: Geom
1941// Topics: Attachments, Masking
1942// See Also: attachable(), position(), attach(), face_profile(), edge_profile_asym(), corner_profile(), edge_mask(), face_mask(), corner_mask()
1943// Usage:
1944//   PARENT() edge_profile([edges], [except], [convexity]) CHILDREN;
1945// Description:
1946//   Takes a 2D mask shape and attaches it to the selected edges, with the appropriate orientation and
1947//   extruded length to be `diff()`ed away, to give the edge a matching profile.  If no tag is set
1948//   then `edge_profile` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1949//   For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges).
1950//   For a step-by-step
1951//   explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1952// Arguments:
1953//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: All edges.
1954//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: No edges.
1955//   excess = Excess length to extrude the profile to make edge masks.  Default: 0.01
1956//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
1957// Side Effects:
1958//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1959//   `$idx` is set to the index number of each edge.
1960//   `$attach_anchor` is set for each edge given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1961//   `$profile_type` is set to `"edge"`.
1962//   `$edge_angle` is set to the inner angle of the current edge.
1963// Example:
1964//   diff()
1965//   cube([50,60,70],center=true)
1966//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
1967//           mask2d_roundover(r=10, inset=2);
1968// Example: Using $edge_angle on a Conoid
1969//   diff()
1970//   cyl(d1=50, d2=30, l=40, anchor=BOT) {
1971//       edge_profile([TOP,BOT], excess=10, convexity=6) {
1972//           mask2d_roundover(r=8, inset=1, excess=1, mask_angle=$edge_angle);
1973//       }
1974//   }
1975// Example: Using $edge_angle on a Prismoid
1976//   diff()
1977//   prismoid([60,50],[30,20],h=40,shift=[-25,15]) {
1978//       edge_profile(excess=10, convexity=20) {
1979//           mask2d_roundover(r=5,inset=1,mask_angle=$edge_angle);
1980//       }
1981//   }
1982
1983module edge_profile(edges=EDGES_ALL, except=[], excess=0.01, convexity=10) {
1984    req_children($children);
1985    check1 = assert($parent_geom != undef, "No object to attach to!");
1986    conoid = $parent_geom[0] == "conoid";
1987    edges = !conoid? _edges(edges, except=except) :
1988        edges==EDGES_ALL? [TOP,BOT] :
1989        assert(all([for (e=edges) in_list(e,[TOP,BOT])]), "Invalid conoid edge spec.")
1990        edges;
1991    vecs = conoid
1992      ? [for (e=edges) e+FWD]
1993      : [
1994            for (i = [0:3], axis=[0:2])
1995            if (edges[axis][i]>0)
1996            EDGE_OFFSETS[axis][i]
1997        ];
1998    all_vecs_are_edges = all([for (vec = vecs) sum(v_abs(vec))==2]);
1999    check2 = assert(all_vecs_are_edges, "All vectors must be edges.");
2000    default_tag("remove")
2001    for ($idx = idx(vecs)) {
2002        vec = vecs[$idx];
2003        anch = _find_anchor(vec, $parent_geom);
2004        path_angs_T = _attach_geom_edge_path($parent_geom, vec);
2005        path = path_angs_T[0];
2006        vecs = path_angs_T[1];
2007        post_T = path_angs_T[2];
2008        $attach_to = undef;
2009        $attach_anchor = anch;
2010        $profile_type = "edge";
2011        multmatrix(post_T) {
2012            for (i = idx(path,e=-2)) {
2013                pt1 = select(path,i);
2014                pt2 = select(path,i+1);
2015                cp = (pt1 + pt2) / 2;
2016                v1 = vecs[i][0];
2017                v2 = vecs[i][1];
2018                $edge_angle = 180 - vector_angle(v1,v2);
2019                if (!approx(pt1,pt2)) {
2020                    seglen = norm(pt2-pt1) + 2 * excess;
2021                    move(cp) {
2022                        frame_map(x=-v2, z=unit(pt2-pt1)) {
2023                            linear_extrude(height=seglen, center=true, convexity=convexity)
2024                                mirror([-1,1]) children();
2025                        }
2026                    }
2027                }
2028            }
2029        }
2030    }
2031}
2032
2033
2034// Module: edge_profile_asym()
2035// Synopsis: Extrudes an asymmetric 2D profile into a mask on the given edges and corners of the parent.
2036// SynTags: Geom
2037// Topics: Attachments, Masking
2038// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), corner_profile(), edge_mask(), face_mask(), corner_mask()
2039// Usage:
2040//   PARENT() edge_profile([edges], [except=], [convexity=], [flip=], [corner_type=]) CHILDREN;
2041// Description:
2042//   Takes an asymmetric 2D mask shape and attaches it to the selected edges and corners, with the appropriate
2043//   orientation and extruded length to be `diff()`ed away, to give the edges and corners a matching profile.
2044//   If no tag is set then `edge_profile_asym()` sets the tag for children to "remove" so that it will work
2045//   with the default {{diff()}} tag.  For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges).
2046//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
2047//   .
2048//   The asymmetric profiles are joined consistently at the corners.  This is impossible if all three edges at a corner use the profile, hence
2049//   this situation is not permitted.  The profile orientation can be inverted using the `flip=true` parameter.
2050//   .
2051//   The standard profiles are located in the first quadrant and have positive X values.  If you provide a profile located in the second quadrant,
2052//   where the X values are negative, then it will produce a fillet.  You can flip any of the standard profiles using {{xflip()}}.  
2053//   Fillets are always asymmetric because at a given edge, they can blend in two different directions, so even for symmetric profiles,
2054//   the asymmetric logic is required.  You can set the `corner_type` parameter to select rounded, chamfered or sharp corners.
2055//   However, when the corners are inside (concave) corners, you must provide the size of the profile ([width,height]), because the
2056//   this information is required to produce the correct corner and cannot be obtain from the profile itself, which is a child object.  
2057// Arguments:
2058//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: All edges.
2059//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: No edges.
2060//   excess = Excess length to extrude the profile to make edge masks.  Default: 0.01
2061//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
2062//   flip = If true, reverses the orientation of any external profile parts at each edge.  Default false
2063//   corner_type = Specifies how exterior corners should be formed.  Must be one of `"none"`, `"chamfer"`, `"round"`, or `"sharp"`.  Default: `"none"`
2064//   size = If given the width and height of the 2D profile, will enable rounding and chamfering of internal corners when given a negative profile.
2065// Side Effects:
2066//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
2067//   `$idx` is set to the index number of each edge.
2068//   `$attach_anchor` is set for each edge given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
2069//   `$profile_type` is set to `"edge"`.
2070//   `$edge_angle` is set to the inner angle of the current edge.
2071// Example:
2072//   ogee = [
2073//       "xstep",1,  "ystep",1,  // Starting shoulder.
2074//       "fillet",5, "round",5,  // S-curve.
2075//       "ystep",1,  "xstep",1   // Ending shoulder.
2076//   ];
2077//   diff()
2078//   cuboid(50) {
2079//       edge_profile_asym(FRONT)
2080//          mask2d_ogee(ogee);
2081//   }
2082// Example: Flipped
2083//   ogee = [
2084//       "xstep",1,  "ystep",1,  // Starting shoulder.
2085//       "fillet",5, "round",5,  // S-curve.
2086//       "ystep",1,  "xstep",1   // Ending shoulder.
2087//   ];
2088//   diff()
2089//   cuboid(50) {
2090//       edge_profile_asym(FRONT, flip=true)
2091//          mask2d_ogee(ogee);
2092//   }
2093// Example: Negative Chamfering
2094//   cuboid(50) {
2095//       edge_profile_asym(FWD, flip=false)
2096//           xflip() mask2d_chamfer(10);
2097//       edge_profile_asym(BACK, flip=true, corner_type="sharp")
2098//           xflip() mask2d_chamfer(10);
2099//   }
2100// Example: Negative Roundings
2101//   cuboid(50) {
2102//       edge_profile_asym(FWD, flip=false)
2103//           xflip() mask2d_roundover(10);
2104//       edge_profile_asym(BACK, flip=true, corner_type="round")
2105//           xflip() mask2d_roundover(10);
2106//   }
2107// Example: Cornerless
2108//   cuboid(50) {
2109//       edge_profile_asym(
2110//           "ALL", except=[TOP+FWD+RIGHT, BOT+BACK+LEFT]
2111//        ) xflip() mask2d_roundover(10);
2112//   }
2113// Example: More complicated edge sets
2114//   cuboid(50) {
2115//       edge_profile_asym(
2116//           [FWD,BACK,BOT+RIGHT], except=[FWD+RIGHT,BOT+BACK],
2117//           corner_type="round"
2118//        ) xflip() mask2d_roundover(10);
2119//   }
2120// Example: Mixing it up a bit.
2121//   diff()
2122//   cuboid(60) {
2123//       tag("keep") edge_profile_asym(LEFT, flip=true, corner_type="chamfer")
2124//           xflip() mask2d_chamfer(10);
2125//       edge_profile_asym(RIGHT)
2126//           mask2d_roundover(10);
2127//   }
2128// Example: Chamfering internal corners.
2129//   cuboid(40) {
2130//       edge_profile_asym(
2131//           [FWD+DOWN,FWD+LEFT],
2132//           corner_type="chamfer", size=[10,10]/sqrt(2)
2133//        ) xflip() mask2d_chamfer(10);
2134//   }
2135// Example: Rounding internal corners.
2136//   cuboid(40) {
2137//       edge_profile_asym(
2138//           [FWD+DOWN,FWD+LEFT],
2139//           corner_type="round", size=[10,10]
2140//        ) xflip() mask2d_roundover(10);
2141//   }
2142
2143module edge_profile_asym(
2144    edges=EDGES_ALL, except=[],
2145    excess=0.01, convexity=10,
2146    flip=false, corner_type="none",
2147    size=[0,0]
2148) {
2149    function _corner_orientation(pos,pvec) =
2150        let(
2151            j = [for (i=[0:2]) if (pvec[i]) i][0],
2152            T = (pos.x>0? xflip() : ident(4)) *
2153                (pos.y>0? yflip() : ident(4)) *
2154                (pos.z>0? zflip() : ident(4)) *
2155                rot(-120*(2-j), v=[1,1,1])
2156        ) T;
2157
2158    function _default_edge_orientation(edge) =
2159        edge.z < 0? [[-edge.x,-edge.y,0], UP] :
2160        edge.z > 0? [[-edge.x,-edge.y,0], DOWN] :
2161        edge.y < 0? [[-edge.x,0,0], BACK] :
2162        [[-edge.x,0,0], FWD] ;
2163
2164    function _edge_transition_needs_flip(from,to) =
2165        let(
2166            flip_edges = [
2167                [BOT+FWD, [FWD+LEFT, FWD+RIGHT]],
2168                [BOT+BACK, [BACK+LEFT, BACK+RIGHT]],
2169                [BOT+LEFT, []],
2170                [BOT+RIGHT, []],
2171                [TOP+FWD, [FWD+LEFT, FWD+RIGHT]],
2172                [TOP+BACK, [BACK+LEFT, BACK+RIGHT]],
2173                [TOP+LEFT, []],
2174                [TOP+RIGHT, []],
2175                [FWD+LEFT, [TOP+FWD, BOT+FWD]],
2176                [FWD+RIGHT, [TOP+FWD, BOT+FWD]],
2177                [BACK+LEFT, [TOP+BACK, BOT+BACK]],
2178                [BACK+RIGHT, [TOP+BACK, BOT+BACK]],
2179            ],
2180            i = search([from], flip_edges, num_returns_per_match=1)[0],
2181            check = assert(i!=[], "Bad edge vector.")
2182        ) in_list(to,flip_edges[i][1]);
2183
2184    function _edge_corner_numbers(vec) =
2185        let(
2186            v2 = [for (i=idx(vec)) vec[i]? (vec[i]+1)/2*pow(2,i) : 0],
2187            off = v2.x + v2.y + v2.z,
2188            xs = [0, if (!vec.x) 1],
2189            ys = [0, if (!vec.y) 2],
2190            zs = [0, if (!vec.z) 4]
2191        ) [for (x=xs, y=ys, z=zs) x+y+z + off];
2192
2193    function _gather_contiguous_edges(edge_corners) =
2194        let(
2195            no_tri_corners = all([for(cn = [0:7]) len([for (ec=edge_corners) if(in_list(cn,ec[1])) 1])<3]),
2196            check = assert(no_tri_corners, "Cannot have three edges that meet at the same corner.")
2197        )
2198        _gather_contiguous_edges_r(
2199            [for (i=idx(edge_corners)) if(i) edge_corners[i]],
2200            edge_corners[0][1],
2201            [edge_corners[0][0]], []);
2202
2203    function _gather_contiguous_edges_r(edge_corners, ecns, curr, out) =
2204        len(edge_corners)==0? [each out, curr] :
2205        let(
2206            i1 = [
2207                for (i = idx(edge_corners))
2208                if (in_list(ecns[0], edge_corners[i][1]))
2209                i
2210            ],
2211            i2 = [
2212                for (i = idx(edge_corners))
2213                if (in_list(ecns[1], edge_corners[i][1]))
2214                i
2215            ]
2216        ) !i1 && !i2? _gather_contiguous_edges_r(
2217            [for (i=idx(edge_corners)) if(i) edge_corners[i]],
2218            edge_corners[0][1],
2219            [edge_corners[0][0]],
2220            [each out, curr]
2221        ) : let(
2222            nu_curr = [
2223                if (i1) edge_corners[i1[0]][0],
2224                each curr,
2225                if (i2) edge_corners[i2[0]][0],
2226            ],
2227            nu_ecns = [
2228                if (!i1) ecns[0] else [
2229                    for (ecn = edge_corners[i1[0]][1])
2230                    if (ecn != ecns[0]) ecn
2231                ][0],
2232                if (!i2) ecns[1] else [
2233                    for (ecn = edge_corners[i2[0]][1])
2234                    if (ecn != ecns[1]) ecn
2235                ][0],
2236            ],
2237            rem = [
2238                for (i = idx(edge_corners))
2239                if (i != i1[0] && i != i2[0])
2240                edge_corners[i]
2241            ]
2242        )
2243        _gather_contiguous_edges_r(rem, nu_ecns, nu_curr, out);
2244
2245    function _edge_transition_inversions(edge_string) =
2246        let(
2247            // boolean cumulative sum
2248            bcs = function(list, i=0, inv=false, out=[])
2249                    i>=len(list)? out :
2250                    let( nu_inv = list[i]? !inv : inv )
2251                    bcs(list, i+1, nu_inv, [each out, nu_inv]),
2252            inverts = bcs([
2253                false,
2254                for(i = idx(edge_string)) if (i)
2255                    _edge_transition_needs_flip(
2256                        edge_string[i-1],
2257                        edge_string[i]
2258                    )
2259            ]),
2260            boti = [for(i = idx(edge_string)) if (edge_string[i].z<0) i],
2261            topi = [for(i = idx(edge_string)) if (edge_string[i].z>0) i],
2262            lfti = [for(i = idx(edge_string)) if (edge_string[i].x<0) i],
2263            rgti = [for(i = idx(edge_string)) if (edge_string[i].x>0) i],
2264            idx = [for (m = [boti, topi, lfti, rgti]) if(m) m[0]][0],
2265            rinverts = inverts[idx] == false? inverts : [for (x = inverts) !x]
2266        ) rinverts;
2267
2268    function _is_closed_edge_loop(edge_string) =
2269        let(
2270            e1 = edge_string[0],
2271            e2 = last(edge_string)
2272        )
2273        len([for (i=[0:2]) if (abs(e1[i])==1 && e1[i]==e2[i]) 1]) == 1 &&
2274        len([for (i=[0:2]) if (e1[i]==0 && abs(e2[i])==1) 1]) == 1 &&
2275        len([for (i=[0:2]) if (e2[i]==0 && abs(e1[i])==1) 1]) == 1;
2276
2277    function _edge_pair_perp_vec(e1,e2) =
2278        [for (i=[0:2]) if (abs(e1[i])==1 && e1[i]==e2[i]) -e1[i] else 0];
2279
2280    req_children($children);
2281    check1 = assert($parent_geom != undef, "No object to attach to!")
2282        assert(in_list(corner_type, ["none", "round", "chamfer", "sharp"]))
2283        assert(is_bool(flip));
2284    edges = _edges(edges, except=except);
2285    vecs = [
2286        for (i = [0:3], axis=[0:2])
2287        if (edges[axis][i]>0)
2288        EDGE_OFFSETS[axis][i]
2289    ];
2290    all_vecs_are_edges = all([for (vec = vecs) sum(v_abs(vec))==2]);
2291    check2 = assert(all_vecs_are_edges, "All vectors must be edges.");
2292    edge_corners = [for (vec = vecs) [vec, _edge_corner_numbers(vec)]];
2293    edge_strings = _gather_contiguous_edges(edge_corners);
2294    default_tag("remove")
2295    for (edge_string = edge_strings) {
2296        inverts = _edge_transition_inversions(edge_string);
2297        flipverts = [for (x = inverts) flip? !x : x];
2298        vecpairs = [
2299            for (i = idx(edge_string))
2300            let (p = _default_edge_orientation(edge_string[i]))
2301            flipverts[i]? [p.y,p.x] : p
2302        ];
2303        is_loop = _is_closed_edge_loop(edge_string);
2304        for (i = idx(edge_string)) {
2305            if (corner_type!="none" && (i || is_loop)) {
2306                e1 = select(edge_string,i-1);
2307                e2 = select(edge_string,i);
2308                vp1 = select(vecpairs,i-1);
2309                vp2 = select(vecpairs,i);
2310                pvec = _edge_pair_perp_vec(e1,e2);
2311                pos = [for (i=[0:2]) e1[i]? e1[i] : e2[i]];
2312                mirT = _corner_orientation(pos, pvec);
2313                $attach_to = undef;
2314                $attach_anchor = _find_anchor(pos, $parent_geom);
2315                $profile_type = "corner";
2316                position(pos) {
2317                    multmatrix(mirT) {
2318                        if (vp1.x == vp2.x && size.y > 0) {
2319                            zflip() {
2320                                if (corner_type=="chamfer") {
2321                                    fn = $fn;
2322                                    move([size.y,size.y]) {
2323                                        rotate_extrude(angle=90, $fn=4)
2324                                            left_half(planar=true, $fn=fn)
2325                                                zrot(-90) fwd(size.y) children();
2326                                    }
2327                                    linear_extrude(height=size.x) {
2328                                        mask2d_roundover(size.y, inset=0.01, $fn=4);
2329                                    }
2330                                } else if (corner_type=="round") {
2331                                    move([size.y,size.y]) {
2332                                        rotate_extrude(angle=90)
2333                                            left_half(planar=true)
2334                                                zrot(-90) fwd(size.y) children();
2335                                    }
2336                                    linear_extrude(height=size.x) {
2337                                        mask2d_roundover(size.y, inset=0.01);
2338                                    }
2339                                }
2340                            }
2341                        } else if (vp1.y == vp2.y) {
2342                            if (corner_type=="chamfer") {
2343                                fn = $fn;
2344                                rotate_extrude(angle=90, $fn=4)
2345                                    right_half(planar=true, $fn=fn)
2346                                        children();
2347                                rotate_extrude(angle=90, $fn=4)
2348                                    left_half(planar=true, $fn=fn)
2349                                        children();
2350                            } else if (corner_type=="round") {
2351                                rotate_extrude(angle=90)
2352                                    right_half(planar=true)
2353                                        children();
2354                                rotate_extrude(angle=90)
2355                                    left_half(planar=true)
2356                                        children();
2357                            } else { //corner_type == "sharp"
2358                                intersection() {
2359                                    rot([90,0, 0]) linear_extrude(height=100,center=true,convexity=convexity) children();
2360                                    rot([90,0,90]) linear_extrude(height=100,center=true,convexity=convexity) children();
2361                                }
2362                            }
2363                        }
2364                    }
2365                }
2366            }
2367        }
2368        for (i = idx(edge_string)) {
2369            $attach_to = undef;
2370            $attach_anchor = _find_anchor(edge_string[i], $parent_geom);
2371            $profile_type = "edge";
2372            edge_profile(edge_string[i], excess=excess, convexity=convexity) {
2373                if (flipverts[i]) {
2374                    mirror([-1,1]) children();
2375                } else {
2376                    children();
2377                }
2378            }
2379        }
2380    }
2381}
2382
2383
2384
2385// Module: corner_profile()
2386// Synopsis: Rotationally extrudes a 2d edge profile into corner mask on the given corners of the parent.
2387// SynTags: Geom
2388// Topics: Attachments, Masking
2389// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), corner_mask(), face_mask(), edge_mask()
2390// Usage:
2391//   PARENT() corner_profile([corners], [except], [r=|d=], [convexity=]) CHILDREN;
2392// Description:
2393//   Takes a 2D mask shape, rotationally extrudes and converts it into a corner mask, and attaches it
2394//   to the selected corners with the appropriate orientation. If no tag is set then `corner_profile()`
2395//   sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
2396//   See [Specifying Corners](attachments.scad#subsection-specifying-corners) for information on how to specify corner sets.
2397//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
2398// Arguments:
2399//   corners = Corners to mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: All corners.
2400//   except = Corners to explicitly NOT mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: No corners.
2401//   ---
2402//   r = Radius of corner mask.
2403//   d = Diameter of corner mask.
2404//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
2405// Side Effects:
2406//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
2407//   `$idx` is set to the index number of each corner.
2408//   `$attach_anchor` is set for each corner given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
2409//   `$profile_type` is set to `"corner"`.
2410// Example:
2411//   diff()
2412//   cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) {
2413//       corner_profile(TOP,r=10)
2414//           mask2d_teardrop(r=10, angle=40);
2415//   }
2416module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) {
2417    check1 = assert($parent_geom != undef, "No object to attach to!");
2418    r = max(0.01, get_radius(r=r, d=d, dflt=undef));
2419    check2 = assert(is_num(r), "Bad r/d argument.");
2420    corners = _corners(corners, except=except);
2421    vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]];
2422    all_vecs_are_corners = all([for (vec = vecs) sum(v_abs(vec))==3]);
2423    check3 = assert(all_vecs_are_corners, "All vectors must be corners.");
2424    for ($idx = idx(vecs)) {
2425        vec = vecs[$idx];
2426        anch = _find_anchor(vec, $parent_geom);
2427        $attach_to = undef;
2428        $attach_anchor = anch;
2429        $profile_type = "corner";
2430        rotang = vec.z<0?
2431            [  0,0,180+v_theta(vec)-45] :
2432            [180,0,-90+v_theta(vec)-45];
2433        default_tag("remove"){
2434            translate(anch[1]) {
2435                rot(rotang) {
2436                    down(0.01) {
2437                        linear_extrude(height=r+0.01, center=false) {
2438                            difference() {
2439                                translate(-[0.01,0.01]) square(r);
2440                                translate([r,r]) circle(r=r*0.999);
2441                            }
2442                        }
2443                    }
2444                    translate([r,r]) zrot(180) {
2445                        rotate_extrude(angle=90, convexity=convexity) {
2446                            right(r) xflip() {
2447                                children();
2448                            }
2449                        }
2450                    }
2451                }
2452            }
2453        }
2454    }
2455}
2456
2457
2458// Section: Making your objects attachable
2459
2460
2461// Module: attachable()
2462// Synopsis: Manages the anchoring, spin, orientation, and attachments for an object.
2463// Topics: Attachments
2464// See Also: reorient()
2465// Usage: Square/Trapezoid Geometry
2466//   attachable(anchor, spin, two_d=true, size=, [size2=], [shift=], [override=], ...) {OBJECT; children();}
2467// Usage: Circle/Oval Geometry
2468//   attachable(anchor, spin, two_d=true, r=|d=, ...) {OBJECT; children();}
2469// Usage: 2D Path/Polygon Geometry
2470//   attachable(anchor, spin, two_d=true, path=, [extent=], ...) {OBJECT; children();}
2471// Usage: 2D Region Geometry
2472//   attachable(anchor, spin, two_d=true, region=, [extent=], ...) {OBJECT; children();}
2473// Usage: Cubical/Prismoidal Geometry
2474//   attachable(anchor, spin, [orient], size=, [size2=], [shift=], [override=],  ...) {OBJECT; children();}
2475// Usage: Cylindrical Geometry
2476//   attachable(anchor, spin, [orient], r=|d=, l=, [axis=], ...) {OBJECT; children();}
2477// Usage: Conical Geometry
2478//   attachable(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], ...) {OBJECT; children();}
2479// Usage: Spheroid/Ovoid Geometry
2480//   attachable(anchor, spin, [orient], r=|d=, ...) {OBJECT; children();}
2481// Usage: Extruded Path/Polygon Geometry
2482//   attachable(anchor, spin, path=, l=|h=, [extent=], ...) {OBJECT; children();}
2483// Usage: Extruded Region Geometry
2484//   attachable(anchor, spin, region=, l=|h=, [extent=], ...) {OBJECT; children();}
2485// Usage: VNF Geometry
2486//   attachable(anchor, spin, [orient], vnf=, [extent=], ...) {OBJECT; children();}
2487// Usage: Pre-Specified Geometry
2488//   attachable(anchor, spin, [orient], geom=) {OBJECT; children();}
2489//
2490// Description:
2491//   Manages the anchoring, spin, orientation, and attachments for OBJECT, located in a 3D volume or 2D area.
2492//   A managed 3D volume is assumed to be vertically (Z-axis) oriented, and centered.
2493//   A managed 2D area is just assumed to be centered.  The shape to be managed is given
2494//   as the first child to this module, and the second child should be given as `children()`.
2495//   For example, to manage a conical shape:
2496//   ```openscad
2497//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h) {
2498//       cyl(r1=r1, r2=r2, l=h);
2499//       children();
2500//   }
2501//   ```
2502//   .
2503//   If this is *not* run as a child of `attach()` with the `to` argument
2504//   given, then the following transformations are performed in order:
2505//   * Translates so the `anchor` point is at the origin (0,0,0).
2506//   * Rotates around the Z axis by `spin` degrees counter-clockwise.
2507//   * Rotates so the top of the part points towards the vector `orient`.
2508//   .
2509//   If this is called as a child of `attach(from,to)`, then the info
2510//   for the anchor points referred to by `from` and `to` are fetched,
2511//   which will include position, direction, and spin.  With that info,
2512//   the following transformations are performed:
2513//   * Translates this part so it's anchor position matches the parent's anchor position.
2514//   * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector.
2515//   * Rotates this part so it's anchor spin matches the parent's anchor spin.
2516//   .
2517//   In addition to handling positioning of the attachable object, 
2518//   this module is also responsible for handing coloring of objects with {{recolor()}} and {{color_this()}}, and
2519//   it is responsible for processing tags and determining whether the object should
2520//   display or not in the current context.  The determination based on the tags of whether to display the attachable object
2521//   often occurs in this module, which means that an object which does not display (e.g. a "remove" tagged object
2522//   inside {{diff()}}) cannot have internal {{tag()}} calls that change its tags and cause submodel
2523//   portions to display: the entire object simply does not run.  If you want the use the attachable object's internal tags outside
2524//   of the attachable object you can set `expose_tags=true` which delays the determination to display objects to the children.
2525//   For this to work correctly, all of the children must be attachables.  An example situation where you should set
2526//   `expose_tags=true` is when you want to have negative space in an attachable object that gets removed from the parent via
2527//   a "remove" tagged component of your attachable.  
2528//   .
2529//   Application of {{recolor()}} and {{color_this()}} also happens in this module and normally it applies to the
2530//   entire attachable object, so coloring commands that you give internally in the first child to `attachable()` have no effect.
2531//   Generally it makes sense that if a user specifies a color for an attachable object, the entire object is displayed
2532//   in that color, but if you want to retain control of color for sub-parts of an attachable object, you can use
2533//   the `keep_color=true` option, which delays the assignment of colors to the child level.  For this to work
2534//   correctly, all of the sub-parts of your attachable object must be attachables.  Also note that this option could
2535//   be confusing to users who don't understand why color commands are not working on the object.  
2536//   .
2537//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
2538//
2539// Arguments:
2540//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2541//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2542//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2543//   ---
2544//   size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height.  If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
2545//   size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume.  If given as a number, contains the back width of the trapezoidal shape.
2546//   shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount.  If given as a number, shifts the back of the trapezoidal shape right by that amount.  Default: No shift.
2547//   r = Radius of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2548//   d = Diameter of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2549//   r1 = Radius of the bottom of the conical volume.  Can be a scalar, or a list of sizes per axis.
2550//   r2 = Radius of the top of the conical volume.  Can be a scalar, or a list of sizes per axis.
2551//   d1 = Diameter of the bottom of the conical volume.  Can be a scalar, a list of sizes per axis.
2552//   d2 = Diameter of the top of the conical volume.  Can be a scalar, a list of sizes per axis.
2553//   l/h = Length of the cylindrical, conical, or extruded path volume along axis.
2554//   vnf = The [VNF](vnf.scad) of the volume.
2555//   path = The path to generate a polygon from.
2556//   region = The region to generate a shape from.
2557//   extent = If true, calculate anchors by extents, rather than intersection, for VNFs and paths.  Default: true.
2558//   cp = If given, specifies the centerpoint of the volume.  Default: `[0,0,0]`
2559//   offset = If given, offsets the perimeter of the volume around the centerpoint.
2560//   anchors = If given as a list of anchor points, allows named anchor points.
2561//   two_d = If true, the attachable shape is 2D.  If false, 3D.  Default: false (3D)
2562//   axis = The vector pointing along the axis of a geometry.  Default: UP
2563//   override = Function that takes an anchor and for 3d returns a triple `[position, direction, spin]` or for 2d returns a pair `[position,direction]` to use for that anchor to override the normal one.  You can also supply a lookup table that is a list of `[anchor, [position, direction, spin]]` entries.  If the direction/position/spin that is returned is undef then the default will be used.  This option applies only to the "trapezoid" and "prismoid" geometry types.  
2564//   geom = If given, uses the pre-defined (via {{attach_geom()}} geometry.
2565//   expose_tags = If true then delay the decision to display or not display this object to the children, which it possible for tags to respond to operations like {{diff()}} used outside the attachble object.  Only works correctly if everything in the attachable is also attachable.  Default: false
2566//   keep_color = If true then delay application of color to the children, which means that externally applied color is overridden by color specified within the attachable.  Only works properly if everything in the attachable is also attacahble.  Default: false
2567//
2568// Side Effects:
2569//   `$parent_anchor` is set to the parent object's `anchor` value.
2570//   `$parent_spin` is set to the parent object's `spin` value.
2571//   `$parent_orient` is set to the parent object's `orient` value.
2572//   `$parent_geom` is set to the parent object's `geom` value.
2573//   `$parent_size` is set to the parent object's cubical `[X,Y,Z]` volume size.
2574//   `$color` is used to set the color of the object
2575//   `$save_color` is used to revert color to the parent's color
2576//
2577// Example(NORENDER): Cubical Shape
2578//   attachable(anchor, spin, orient, size=size) {
2579//       cube(size, center=true);
2580//       children();
2581//   }
2582//
2583// Example(NORENDER): Prismoidal Shape
2584//   attachable(
2585//       anchor, spin, orient,
2586//       size=point3d(botsize,h),
2587//       size2=topsize,
2588//       shift=shift
2589//   ) {
2590//       prismoid(botsize, topsize, h=h, shift=shift);
2591//       children();
2592//   }
2593//
2594// Example(NORENDER): Cylindrical Shape, Z-Axis Aligned
2595//   attachable(anchor, spin, orient, r=r, l=h) {
2596//       cyl(r=r, l=h);
2597//       children();
2598//   }
2599//
2600// Example(NORENDER): Cylindrical Shape, Y-Axis Aligned
2601//   attachable(anchor, spin, orient, r=r, l=h, axis=BACK) {
2602//       cyl(r=r, l=h);
2603//       children();
2604//   }
2605//
2606// Example(NORENDER): Cylindrical Shape, X-Axis Aligned
2607//   attachable(anchor, spin, orient, r=r, l=h, axis=RIGHT) {
2608//       cyl(r=r, l=h);
2609//       children();
2610//   }
2611//
2612// Example(NORENDER): Conical Shape, Z-Axis Aligned
2613//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h) {
2614//       cyl(r1=r1, r2=r2, l=h);
2615//       children();
2616//   }
2617//
2618// Example(NORENDER): Conical Shape, Y-Axis Aligned
2619//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h, axis=BACK) {
2620//       cyl(r1=r1, r2=r2, l=h);
2621//       children();
2622//   }
2623//
2624// Example(NORENDER): Conical Shape, X-Axis Aligned
2625//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h, axis=RIGHT) {
2626//       cyl(r1=r1, r2=r2, l=h);
2627//       children();
2628//   }
2629//
2630// Example(NORENDER): Spherical Shape
2631//   attachable(anchor, spin, orient, r=r) {
2632//       sphere(r=r);
2633//       children();
2634//   }
2635//
2636// Example(NORENDER): Extruded Polygon Shape, by Extents
2637//   attachable(anchor, spin, orient, path=path, l=length) {
2638//       linear_extrude(height=length, center=true)
2639//           polygon(path);
2640//       children();
2641//   }
2642//
2643// Example(NORENDER): Extruded Polygon Shape, by Intersection
2644//   attachable(anchor, spin, orient, path=path, l=length, extent=false) {
2645//       linear_extrude(height=length, center=true)
2646//           polygon(path);
2647//       children();
2648//   }
2649//
2650// Example(NORENDER): Arbitrary VNF Shape, by Extents
2651//   attachable(anchor, spin, orient, vnf=vnf) {
2652//       vnf_polyhedron(vnf);
2653//       children();
2654//   }
2655//
2656// Example(NORENDER): Arbitrary VNF Shape, by Intersection
2657//   attachable(anchor, spin, orient, vnf=vnf, extent=false) {
2658//       vnf_polyhedron(vnf);
2659//       children();
2660//   }
2661//
2662// Example(NORENDER): 2D Rectangular Shape
2663//   attachable(anchor, spin, orient, two_d=true, size=size) {
2664//       square(size, center=true);
2665//       children();
2666//   }
2667//
2668// Example(NORENDER): 2D Trapezoidal Shape
2669//   attachable(
2670//       anchor, spin, orient,
2671//       two_d=true,
2672//       size=[x1,y],
2673//       size2=x2,
2674//       shift=shift
2675//   ) {
2676//       trapezoid(w1=x1, w2=x2, h=y, shift=shift);
2677//       children();
2678//   }
2679//
2680// Example(NORENDER): 2D Circular Shape
2681//   attachable(anchor, spin, orient, two_d=true, r=r) {
2682//       circle(r=r);
2683//       children();
2684//   }
2685//
2686// Example(NORENDER): Arbitrary 2D Polygon Shape, by Extents
2687//   attachable(anchor, spin, orient, two_d=true, path=path) {
2688//       polygon(path);
2689//       children();
2690//   }
2691//
2692// Example(NORENDER): Arbitrary 2D Polygon Shape, by Intersection
2693//   attachable(anchor, spin, orient, two_d=true, path=path, extent=false) {
2694//       polygon(path);
2695//       children();
2696//   }
2697//
2698// Example(NORENDER): Using Pre-defined Geometry
2699//   geom = atype=="perim"? attach_geom(two_d=true, path=path, extent=false) :
2700//       atype=="extents"? attach_geom(two_d=true, path=path, extent=true) :
2701//       atype=="circle"? attach_geom(two_d=true, r=r) :
2702//       assert(false, "Bad atype");
2703//   attachable(anchor, spin, orient, geom=geom) {
2704//       polygon(path);
2705//       children();
2706//   }
2707//
2708// Example: An object can be designed to attach as negative space using {{diff()}}, but if you want an object to include both positive and negative space then you run into trouble because tags inside the `attachable()` are ignored.  One solution is to call attachable() twice.  This example shows how two calls to  attachable can create an object with positive and negative space.  Note, however, that children in the negative space are differenced away: the highlighted little cube does not survive into the final model.
2709//   module thing(anchor,spin,orient) {
2710//      tag("remove") attachable(size=[15,15,15],anchor=anchor,spin=spin,orient=orient){
2711//        cuboid([10,10,16]);
2712//        union(){}   // dummy children
2713//      }
2714//      attachable(size=[15,15,15], anchor=anchor, spin=spin, orient=orient){
2715//        cuboid([15,15,15]);
2716//        children();
2717//      }
2718//   }
2719//   diff()
2720//     cube([19,10,19])
2721//       attach([FRONT],overlap=-4)
2722//         thing(anchor=TOP)
2723//           # attach(TOP) cuboid(2,anchor=TOP);
2724// Example: Here is an example where the "keep" tag allows children to appear in the negative space.  That tag is also needed for this module to produce the desired output.  As above, the tag must be applied outside the attachable() call.
2725//   module thing(anchor = CENTER, spin = 0, orient = UP) {
2726//      tag("remove") attachable(anchor, spin, orient, d1=0,d2=95,h=33) {
2727//          cylinder(h = 33.1, d1 = 0, d2 = 95, anchor=CENTER);
2728//          union(){}  // dummy children
2729//      }
2730//      tag("keep") attachable(anchor, spin, orient,d1=0,d2=95,h=33) {
2731//            cylinder(h = 33, d = 10,anchor=CENTER);
2732//            children();
2733//        }
2734//    }
2735//    diff()
2736//      cube(100)
2737//        attach([FRONT,TOP],overlap=-4)
2738//          thing(anchor=TOP)
2739//            tube(ir=12,h=10);
2740// Example: A different way to achieve similar effects to the above to examples is to use the `expose_tags` parameter.  This parameter allows you to use just one call to attachable.  The second example above can also be rewritten like this. 
2741//   module thing(anchor,spin,orient) {
2742//      attachable(size=[15,15,15],anchor=anchor,spin=spin,orient=orient,expose_tags=true){
2743//        union(){
2744//          cuboid([15,15,15]);
2745//          tag("remove")cuboid([10,10,16]);
2746//        }
2747//        children();
2748//      }
2749//   }
2750//   diff()
2751//     cube([19,10,19])
2752//       attach([FRONT],overlap=-4)
2753//         thing(anchor=TOP);
2754// Example: An advantage of using `expose_tags` is that it can work on nested constructions.  Here the child cylinder is aligned relative to its parent and removed from the calling parent object.
2755//   $fn=64;
2756//   module thing(anchor=BOT){
2757//     attachable(anchor = anchor,d=9,h=6,expose_tags=true){
2758//       cyl(d = 9, h = 6) 
2759//         tag("remove") 
2760//            align(RIGHT+TOP,inside=true) 
2761//                 left(1)up(1)cyl(l=11, d=3);
2762//       children();
2763//     }
2764//   }
2765//   back_half()
2766//     diff()
2767//       cuboid(10)
2768//         position(TOP)thing(anchor=BOT);
2769// Example(3D,NoAxes): Here an attachable module uses {{recolor()}} to change the color of a sub-part, producing the result shown on the left.  But if the caller applies color to the attachable, then both the green and yellow are changed, as shown on the right.  
2770//   module thing(anchor=CENTER) {
2771//       attachable(anchor,size=[10,10,10]) {
2772//           cuboid(10)
2773//             position(TOP) recolor("green")
2774//               cuboid(5,anchor=BOT);
2775//           children();
2776//       }
2777//   }
2778//   move([-15,-15])
2779//   thing()
2780//     attach(RIGHT,BOT)
2781//       recolor("blue") cyl(d=5,h=5);
2782//   recolor("pink") thing()
2783//     attach(RIGHT,BOT)
2784//       recolor("blue") cyl(d=5,h=5);
2785// Example(3D,NoAxes): Using the `keep_color=true` option enables the green color to persist, even when the user specifies a color.
2786//   module thing(anchor=CENTER) {
2787//       attachable(anchor,size=[10,10,10],keep_color=true) {
2788//           cuboid(10)
2789//             position(TOP) recolor("green")
2790//               cuboid(5,anchor=BOT);
2791//           children();
2792//       }
2793//   }
2794//   recolor("pink") thing()
2795//     attach(RIGHT,BOT)
2796//       recolor("blue") cyl(d=5,h=5);
2797
2798module attachable(
2799    anchor, spin, orient,
2800    size, size2, shift,
2801    r,r1,r2, d,d1,d2, l,h,
2802    vnf, path, region,
2803    extent=true,
2804    cp=[0,0,0],
2805    offset=[0,0,0],
2806    anchors=[],
2807    two_d=false,
2808    axis=UP,override,
2809    geom,
2810    expose_tags=false, keep_color=false
2811) { 
2812    dummy1 =
2813        assert($children==2, "attachable() expects exactly two children; the shape to manage, and the union of all attachment candidates.")
2814        assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
2815        assert(is_undef(spin)   || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
2816        assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient));
2817    anchor = first_defined([anchor, CENTER]);
2818    spin =   default(spin,   0);
2819    orient = is_def($anchor_override)? UP : default(orient, UP);
2820    region = !is_undef(region)? region :
2821        !is_undef(path)? [path] :
2822        undef;
2823    geom = is_def(geom)? geom :
2824        attach_geom(
2825            size=size, size2=size2, shift=shift,
2826            r=r, r1=r1, r2=r2, h=h,
2827            d=d, d1=d1, d2=d2, l=l,
2828            vnf=vnf, region=region, extent=extent,
2829            cp=cp, offset=offset, anchors=anchors,
2830            two_d=two_d, axis=axis, override=override
2831        );
2832    m = _attach_transform(anchor,spin,orient,geom);
2833    multmatrix(m) {
2834        $parent_anchor = anchor;
2835        $parent_spin   = spin;
2836        $parent_orient = orient;
2837        $parent_geom   = geom;
2838        $parent_size   = _attach_geom_size(geom);
2839        $attach_to   = undef;
2840        $anchor_override=undef;
2841        $attach_alignment=undef;
2842        if (expose_tags || _is_shown()){
2843            if (!keep_color)
2844                _color($color) children(0);
2845            else {
2846                $save_color=undef; // Force color_this() color in effect to persist for the entire object
2847                children(0); 
2848            }
2849        }
2850        if (is_def($save_color)) {
2851            $color=$save_color;    // Revert to the color before color_this() call
2852            $save_color=undef;
2853            children(1);
2854        }
2855        else children(1);
2856    }
2857}
2858
2859// Function: reorient()
2860// Synopsis: Calculates the transformation matrix needed to reorient an object.
2861// SynTags: Trans, Path, VNF
2862// Topics: Attachments
2863// See Also: reorient(), attachable()
2864// Usage: Square/Trapezoid Geometry
2865//   mat = reorient(anchor, spin, [orient], two_d=true, size=, [size2=], [shift=], ...);
2866//   pts = reorient(anchor, spin, [orient], two_d=true, size=, [size2=], [shift=], p=, ...);
2867// Usage: Circle/Oval Geometry
2868//   mat = reorient(anchor, spin, [orient], two_d=true, r=|d=, ...);
2869//   pts = reorient(anchor, spin, [orient], two_d=true, r=|d=, p=, ...);
2870// Usage: 2D Path/Polygon Geometry
2871//   mat = reorient(anchor, spin, [orient], two_d=true, path=, [extent=], ...);
2872//   pts = reorient(anchor, spin, [orient], two_d=true, path=, [extent=], p=, ...);
2873// Usage: 2D Region/Polygon Geometry
2874//   mat = reorient(anchor, spin, [orient], two_d=true, region=, [extent=], ...);
2875//   pts = reorient(anchor, spin, [orient], two_d=true, region=, [extent=], p=, ...);
2876// Usage: Cubical/Prismoidal Geometry
2877//   mat = reorient(anchor, spin, [orient], size=, [size2=], [shift=], ...);
2878//   vnf = reorient(anchor, spin, [orient], size=, [size2=], [shift=], p=, ...);
2879// Usage: Cylindrical Geometry
2880//   mat = reorient(anchor, spin, [orient], r=|d=, l=, [axis=], ...);
2881//   vnf = reorient(anchor, spin, [orient], r=|d=, l=, [axis=], p=, ...);
2882// Usage: Conical Geometry
2883//   mat = reorient(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], ...);
2884//   vnf = reorient(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], p=, ...);
2885// Usage: Spheroid/Ovoid Geometry
2886//   mat = reorient(anchor, spin, [orient], r|d=, ...);
2887//   vnf = reorient(anchor, spin, [orient], r|d=, p=, ...);
2888// Usage: Extruded Path/Polygon Geometry
2889//   mat = reorient(anchor, spin, [orient], path=, l=|h=, [extent=], ...);
2890//   vnf = reorient(anchor, spin, [orient], path=, l=|h=, [extent=], p=, ...);
2891// Usage: Extruded Region Geometry
2892//   mat = reorient(anchor, spin, [orient], region=, l=|h=, [extent=], ...);
2893//   vnf = reorient(anchor, spin, [orient], region=, l=|h=, [extent=], p=, ...);
2894// Usage: VNF Geometry
2895//   mat = reorient(anchor, spin, [orient], vnf, [extent], ...);
2896//   vnf = reorient(anchor, spin, [orient], vnf, [extent], p=, ...);
2897//
2898// Description:
2899//   Given anchor, spin, orient, and general geometry info for a managed volume, this calculates
2900//   the transformation matrix needed to be applied to the contents of that volume.  A managed 3D
2901//   volume is assumed to be vertically (Z-axis) oriented, and centered.  A managed 2D area is just
2902//   assumed to be centered.
2903//   .
2904//   If `p` is not given, then the transformation matrix will be returned.
2905//   If `p` contains a VNF, a new VNF will be returned with the vertices transformed by the matrix.
2906//   If `p` contains a path, a new path will be returned with the vertices transformed by the matrix.
2907//   If `p` contains a point, a new point will be returned, transformed by the matrix.
2908//   .
2909//   If `$attach_to` is not defined, then the following transformations are performed in order:
2910//   * Translates so the `anchor` point is at the origin (0,0,0).
2911//   * Rotates around the Z axis by `spin` degrees counter-clockwise.
2912//   * Rotates so the top of the part points towards the vector `orient`.
2913//   .
2914//   If `$attach_to` is defined, as a consequence of `attach(from,to)`, then
2915//   the following transformations are performed in order:
2916//   * Translates this part so it's anchor position matches the parent's anchor position.
2917//   * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector.
2918//   * Rotates this part so it's anchor spin matches the parent's anchor spin.
2919//   .
2920//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
2921//
2922// Arguments:
2923//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2924//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2925//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2926//   ---
2927//   size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height.  If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
2928//   size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume.  If given as a number, contains the back width of the trapezoidal shape.
2929//   shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount.  If given as a number, shifts the back of the trapezoidal shape right by that amount.  Default: No shift.
2930//   r = Radius of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2931//   d = Diameter of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2932//   r1 = Radius of the bottom of the conical volume.  Can be a scalar, or a list of sizes per axis.
2933//   r2 = Radius of the top of the conical volume.  Can be a scalar, or a list of sizes per axis.
2934//   d1 = Diameter of the bottom of the conical volume.  Can be a scalar, a list of sizes per axis.
2935//   d2 = Diameter of the top of the conical volume.  Can be a scalar, a list of sizes per axis.
2936//   l/h = Length of the cylindrical, conical, or extruded path volume along axis.
2937//   vnf = The [VNF](vnf.scad) of the volume.
2938//   path = The path to generate a polygon from.
2939//   region = The region to generate a shape from.
2940//   extent = If true, calculate anchors by extents, rather than intersection.  Default: false.
2941//   cp = If given, specifies the centerpoint of the volume.  Default: `[0,0,0]`
2942//   offset = If given, offsets the perimeter of the volume around the centerpoint.
2943//   anchors = If given as a list of anchor points, allows named anchor points.
2944//   two_d = If true, the attachable shape is 2D.  If false, 3D.  Default: false (3D)
2945//   axis = The vector pointing along the axis of a geometry.  Default: UP
2946//   p = The VNF, path, or point to transform.
2947function reorient(
2948    anchor, spin, orient,
2949    size, size2, shift,
2950    r,r1,r2, d,d1,d2, l,h,
2951    vnf, path, region,
2952    extent=true,
2953    offset=[0,0,0],
2954    cp=[0,0,0],
2955    anchors=[],
2956    two_d=false,
2957    axis=UP, override, 
2958    geom,
2959    p=undef
2960) = 
2961    assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
2962    assert(is_undef(spin)   || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
2963    assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient))
2964    let(
2965        anchor = default(anchor, CENTER),
2966        spin =   default(spin,   0),
2967        orient = default(orient, UP),
2968        region = !is_undef(region)? region :
2969            !is_undef(path)? [path] :
2970            undef,
2971        geom = is_def(geom)? geom :
2972            attach_geom(
2973                size=size, size2=size2, shift=shift,
2974                r=r, r1=r1, r2=r2, h=h,
2975                d=d, d1=d1, d2=d2, l=l,
2976                vnf=vnf, region=region, extent=extent,
2977                cp=cp, offset=offset, anchors=anchors,
2978                two_d=two_d, axis=axis, override=override
2979            ),
2980        $attach_to = undef,
2981        $anchor_override= undef,
2982        $attach_alignment = undef
2983    ) _attach_transform(anchor,spin,orient,geom,p);
2984
2985
2986// Function: named_anchor()
2987// Synopsis: Creates an anchor data structure.
2988// Topics: Attachments
2989// See Also: reorient(), attachable()
2990// Usage:
2991//   a = named_anchor(name, pos, [orient], [spin]);
2992//   a = named_anchor(name, [pos], rot=, [flip=]);
2993// Description:
2994//   Creates an anchor data structure.  You can specify the position, orient direction and spin directly.
2995//   Alternatively for the 3D case you can give a 4x4 rotation matrix which can specify the orient and spin, and optionally
2996//   the position, using a translation component of the matrix.  If you specify `pos` along with `rot` then the position you
2997//   give overrides any translation included in `rot`.  For a step-by-step explanation of attachments,
2998//   see the [Attachments Tutorial](Tutorial-Attachments).
2999// Arguments:
3000//   name = The string name of the anchor.  Lowercase.  Words separated by single dashes.  No spaces.
3001//   pos = The [X,Y,Z] position of the anchor.
3002//   orient = A vector pointing in the direction parts should project from the anchor position.  Default: UP
3003//   spin = If needed, the angle to rotate the part around the direction vector.  Default: 0
3004//   ---
3005//   rot = A 4x4 rotations matrix, which may include a translation
3006//   flip = If true, flip the anchor the opposite direction.  Default: false
3007function named_anchor(name, pos, orient, spin, rot, flip) =
3008  assert(num_defined([orient,spin])==0 || num_defined([rot,flip])==0, "Cannot mix orient or spin with rot or flip")
3009  assert(num_defined([pos,rot])>0, "Must give pos or rot")
3010  is_undef(rot) ? [name, pos, default(orient,UP), default(spin,0)]
3011 : 
3012  let(
3013      flip = default(flip,false),
3014      pos = default(pos,apply(rot,CTR)),
3015      rotpart = _force_rot(rot),
3016      dummy = assert(approx(det4(rotpart),1), "Input rotation is not a rotation matrix"),
3017      dir = flip ? apply(rotpart,DOWN)
3018                 : apply(rotpart,UP),
3019      rot = flip? affine3d_rot_by_axis(apply(rotpart,BACK),180)*rot
3020                      : rot,
3021      decode=rot_decode(rot(to=UP,from=dir)*_force_rot(rot)),
3022      spin = decode[0]*sign(decode[1].z)
3023  )
3024  [name, pos, dir, spin];
3025  
3026
3027function _force_rot(T) =
3028   [for(i=[0:3])
3029       [for(j=[0:3]) j<3 ? T[i][j] :
3030                     i==3 ? 1
3031                       : 0]];
3032
3033// Function: attach_geom()
3034// Synopsis: Returns the internal geometry description of an attachable object.
3035// Topics: Attachments
3036// See Also: reorient(), attachable()
3037// Usage: Null/Point Geometry
3038//   geom = attach_geom(...);
3039// Usage: Square/Trapezoid Geometry
3040//   geom = attach_geom(two_d=true, size=, [size2=], [shift=], ...);
3041// Usage: Circle/Oval Geometry
3042//   geom = attach_geom(two_d=true, r=|d=, ...);
3043// Usage: 2D Path/Polygon/Region Geometry
3044//   geom = attach_geom(two_d=true, region=, [extent=], ...);
3045// Usage: Cubical/Prismoidal Geometry
3046//   geom = attach_geom(size=, [size2=], [shift=], ...);
3047// Usage: Cylindrical Geometry
3048//   geom = attach_geom(r=|d=, l=|h=, [axis=], ...);
3049// Usage: Conical Geometry
3050//   geom = attach_geom(r1|d1=, r2=|d2=, l=, [axis=], ...);
3051// Usage: Spheroid/Ovoid Geometry
3052//   geom = attach_geom(r=|d=, ...);
3053// Usage: Extruded 2D Path/Polygon/Region Geometry
3054//   geom = attach_geom(region=, l=|h=, [extent=], [shift=], [scale=], [twist=], ...);
3055// Usage: VNF Geometry
3056//   geom = attach_geom(vnf=, [extent=], ...);
3057//
3058// Description:
3059//   Given arguments that describe the geometry of an attachable object, returns the internal geometry description.
3060//   This will probably not not ever need to be called by the end user.
3061//
3062// Arguments:
3063//   ---
3064//   size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height.  If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
3065//   size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume.  If given as a number, contains the back width of the trapezoidal shape.
3066//   shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount.  If given as a number, shifts the back of the trapezoidal shape right by that amount.  Default: No shift.
3067//   scale = If given as number or a 2D vector, scales the top of the shape, relative to the bottom.  Default: `[1,1]`
3068//   twist = If given as number, rotates the top of the shape by the given number of degrees clockwise, relative to the bottom.  Default: `0`
3069//   r = Radius of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
3070//   d = Diameter of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
3071//   r1 = Radius of the bottom of the conical volume.  Can be a scalar, or a list of sizes per axis.
3072//   r2 = Radius of the top of the conical volume.  Can be a scalar, or a list of sizes per axis.
3073//   d1 = Diameter of the bottom of the conical volume.  Can be a scalar, a list of sizes per axis.
3074//   d2 = Diameter of the top of the conical volume.  Can be a scalar, a list of sizes per axis.
3075//   l/h = Length of the cylindrical, conical or extruded region volume along axis.
3076//   vnf = The [VNF](vnf.scad) of the volume.
3077//   region = The region to generate a shape from.
3078//   extent = If true, calculate anchors by extents, rather than intersection.  Default: true.
3079//   cp = If given, specifies the centerpoint of the volume.  Default: `[0,0,0]`
3080//   offset = If given, offsets the perimeter of the volume around the centerpoint.
3081//   anchors = If given as a list of anchor points, allows named anchor points.
3082//   two_d = If true, the attachable shape is 2D.  If false, 3D.  Default: false (3D)
3083//   axis = The vector pointing along the axis of a geometry.  Default: UP
3084//   override = Function that takes an anchor and returns a pair `[position,direction]` to use for that anchor to override the normal one.  You can also supply a lookup table that is a list of `[anchor, [position, direction]]` entries.  If the direction/position that is returned is undef then the default will be used.
3085//
3086// Example(NORENDER): Null/Point Shape
3087//   geom = attach_geom();
3088//
3089// Example(NORENDER): Cubical Shape
3090//   geom = attach_geom(size=size);
3091//
3092// Example(NORENDER): Prismoidal Shape
3093//   geom = attach_geom(
3094//       size=point3d(botsize,h),
3095//       size2=topsize, shift=shift
3096//   );
3097//
3098// Example(NORENDER): Cylindrical Shape, Z-Axis Aligned
3099//   geom = attach_geom(r=r, h=h);
3100//
3101// Example(NORENDER): Cylindrical Shape, Y-Axis Aligned
3102//   geom = attach_geom(r=r, h=h, axis=BACK);
3103//
3104// Example(NORENDER): Cylindrical Shape, X-Axis Aligned
3105//   geom = attach_geom(r=r, h=h, axis=RIGHT);
3106//
3107// Example(NORENDER): Conical Shape, Z-Axis Aligned
3108//   geom = attach_geom(r1=r1, r2=r2, h=h);
3109//
3110// Example(NORENDER): Conical Shape, Y-Axis Aligned
3111//   geom = attach_geom(r1=r1, r2=r2, h=h, axis=BACK);
3112//
3113// Example(NORENDER): Conical Shape, X-Axis Aligned
3114//   geom = attach_geom(r1=r1, r2=r2, h=h, axis=RIGHT);
3115//
3116// Example(NORENDER): Spherical Shape
3117//   geom = attach_geom(r=r);
3118//
3119// Example(NORENDER): Ovoid Shape
3120//   geom = attach_geom(r=[r_x, r_y, r_z]);
3121//
3122// Example(NORENDER): Arbitrary VNF Shape, Anchored by Extents
3123//   geom = attach_geom(vnf=vnf);
3124//
3125// Example(NORENDER): Arbitrary VNF Shape, Anchored by Intersection
3126//   geom = attach_geom(vnf=vnf, extent=false);
3127//
3128// Example(NORENDER): 2D Rectangular Shape
3129//   geom = attach_geom(two_d=true, size=size);
3130//
3131// Example(NORENDER): 2D Trapezoidal Shape
3132//   geom = attach_geom(two_d=true, size=[x1,y], size2=x2, shift=shift, override=override);
3133//
3134// Example(NORENDER): 2D Circular Shape
3135//   geom = attach_geom(two_d=true, r=r);
3136//
3137// Example(NORENDER): 2D Oval Shape
3138//   geom = attach_geom(two_d=true, r=[r_x, r_y]);
3139//
3140// Example(NORENDER): Arbitrary 2D Region Shape, Anchored by Extents
3141//   geom = attach_geom(two_d=true, region=region);
3142//
3143// Example(NORENDER): Arbitrary 2D Region Shape, Anchored by Intersection
3144//   geom = attach_geom(two_d=true, region=region, extent=false);
3145//
3146// Example(NORENDER): Extruded Region, Anchored by Extents
3147//   geom = attach_geom(region=region, l=height);
3148//
3149// Example(NORENDER): Extruded Region, Anchored by Intersection
3150//   geom = attach_geom(region=region, l=length, extent=false);
3151//
3152
3153function _local_struct_val(struct, key)=
3154    assert(is_def(key),"key is missing")
3155    let(ind = search([key],struct)[0])
3156    ind == [] ? undef : struct[ind][1];
3157
3158
3159function attach_geom(
3160    size, size2,
3161    shift, scale, twist,
3162    r,r1,r2, d,d1,d2, l,h,
3163    vnf, region,
3164    extent=true,
3165    cp=[0,0,0],
3166    offset=[0,0,0],
3167    anchors=[],
3168    two_d=false,
3169    axis=UP, override
3170) =
3171    assert(is_bool(extent))
3172    assert(is_vector(cp) || is_string(cp))
3173    assert(is_vector(offset))
3174    assert(is_list(anchors))
3175    assert(is_bool(two_d))
3176    assert(is_vector(axis))
3177    !is_undef(size)? (
3178        let(
3179            over_f = is_undef(override) ? function(anchor) [undef,undef,undef]
3180                   : is_func(override) ? override
3181                   : function(anchor) _local_struct_val(override,anchor)
3182        )
3183        two_d? (
3184            let(
3185                size2 = default(size2, size.x),
3186                shift = default(shift, 0)
3187            )
3188            assert(is_vector(size,2))
3189            assert(is_num(size2))
3190            assert(is_num(shift))
3191            ["trapezoid", point2d(size), size2, shift, over_f, cp, offset, anchors]
3192        ) : (
3193            let(
3194                size2 = default(size2, point2d(size)),
3195                shift = default(shift, [0,0])
3196            )
3197            assert(is_vector(size,3))
3198            assert(is_vector(size2,2))
3199            assert(is_vector(shift,2))
3200            ["prismoid", size, size2, shift, axis, over_f, cp, offset, anchors]
3201        )
3202    ) : !is_undef(vnf)? (
3203        assert(is_vnf(vnf))
3204        assert(two_d == false)
3205        extent? ["vnf_extent", vnf, cp, offset, anchors] :
3206        ["vnf_isect", vnf, cp, offset, anchors]
3207    ) : !is_undef(region)? (
3208        assert(is_region(region),2)
3209        let( l = default(l, h) )
3210        two_d==true
3211          ? assert(is_undef(l))
3212            extent==true
3213              ? ["rgn_extent", region, cp, offset, anchors]
3214              : ["rgn_isect",  region, cp, offset, anchors]
3215          : assert(is_finite(l))
3216            let(
3217                shift = default(shift, [0,0]),
3218                scale = is_num(scale)? [scale,scale] : default(scale, [1,1]),
3219                twist = default(twist, 0)
3220            )
3221            assert(is_vector(shift,2))
3222            assert(is_vector(scale,2))
3223            assert(is_num(twist))
3224            extent==true
3225              ? ["extrusion_extent", region, l, twist, scale, shift, cp, offset, anchors]
3226              : ["extrusion_isect",  region, l, twist, scale, shift, cp, offset, anchors]
3227    ) :
3228    let(
3229        r1 = get_radius(r1=r1,d1=d1,r=r,d=d,dflt=undef)
3230    )
3231    !is_undef(r1)? (
3232        let( l = default(l, h) )
3233        !is_undef(l)? (
3234            let(
3235                shift = default(shift, [0,0]),
3236                r2 = get_radius(r1=r2,d1=d2,r=r,d=d,dflt=undef)
3237            )
3238            assert(is_num(r1) || is_vector(r1,2))
3239            assert(is_num(r2) || is_vector(r2,2))
3240            assert(is_num(l))
3241            assert(is_vector(shift,2))
3242            ["conoid", r1, r2, l, shift, axis, cp, offset, anchors]
3243        ) : (
3244            two_d? (
3245                assert(is_num(r1) || is_vector(r1,2))
3246                ["ellipse", r1, cp, offset, anchors]
3247            ) : (
3248                assert(is_num(r1) || is_vector(r1,3))
3249                ["spheroid", r1, cp, offset, anchors]
3250            )
3251        )
3252    ) :
3253    ["point", cp, offset, anchors];
3254
3255
3256
3257
3258
3259
3260//////////////////////////////////////////////////////////////////////////////////////////////////////////////
3261//
3262// Attachment internal functions
3263
3264
3265/// Internal Function: _attach_geom_2d()
3266/// Topics: Attachments
3267/// See Also: reorient(), attachable()
3268/// Usage:
3269///   bool = _attach_geom_2d(geom);
3270/// Description:
3271///   Returns true if the given attachment geometry description is for a 2D shape.
3272function _attach_geom_2d(geom) =
3273    let( type = geom[0] )
3274    type == "trapezoid" || type == "ellipse" ||
3275    type == "rgn_isect" || type == "rgn_extent";
3276
3277
3278/// Internal Function: _attach_geom_size()
3279/// Usage:
3280///   bounds = _attach_geom_size(geom);
3281/// Topics: Attachments
3282/// See Also: reorient(), attachable()
3283/// Description:
3284///   Returns the `[X,Y,Z]` bounding size for the given attachment geometry description.
3285function _attach_geom_size(geom) =
3286    let( type = geom[0] )
3287    type == "point"? [0,0,0] :
3288    type == "prismoid"? ( //size, size2, shift, axis
3289        let(
3290            size=geom[1], size2=geom[2], shift=point2d(geom[3]),
3291            maxx = max(size.x,size2.x),
3292            maxy = max(size.y,size2.y),
3293            z = size.z
3294        ) [maxx, maxy, z]
3295    ) : type == "conoid"? ( //r1, r2, l, shift
3296        let(
3297            r1=geom[1], r2=geom[2], l=geom[3],
3298            shift=point2d(geom[4]), axis=point3d(geom[5]),
3299            rx1 = default(r1[0],r1),
3300            ry1 = default(r1[1],r1),
3301            rx2 = default(r2[0],r2),
3302            ry2 = default(r2[1],r2),
3303            maxxr = max(rx1,rx2),
3304            maxyr = max(ry1,ry2)
3305        )
3306        approx(axis,UP)? [2*maxxr,2*maxyr,l] :
3307        approx(axis,RIGHT)? [l,2*maxyr,2*maxxr] :
3308        approx(axis,BACK)? [2*maxxr,l,2*maxyr] :
3309        [2*maxxr, 2*maxyr, l]
3310    ) : type == "spheroid"? ( //r
3311        let( r=geom[1] )
3312        is_num(r)? [2,2,2]*r : v_mul([2,2,2],point3d(r))
3313    ) : type == "vnf_extent" || type=="vnf_isect"? ( //vnf
3314        let(
3315            vnf = geom[1]
3316        ) vnf==EMPTY_VNF? [0,0,0] :
3317        let(
3318            mm = pointlist_bounds(geom[1][0]),
3319            delt = mm[1]-mm[0]
3320        ) delt
3321    ) : type == "extrusion_isect" || type == "extrusion_extent"? ( //path, l
3322        let(
3323            mm = pointlist_bounds(flatten(geom[1])),
3324            delt = mm[1]-mm[0]
3325        ) [delt.x, delt.y, geom[2]]
3326    ) : type == "trapezoid"? ( //size, size2
3327        let(
3328            size=geom[1], size2=geom[2], shift=geom[3],
3329            maxx = max(size.x,size2+abs(shift))
3330        ) [maxx, size.y]
3331    ) : type == "ellipse"? ( //r
3332        let( r=geom[1] )
3333        is_num(r)? [2,2]*r : v_mul([2,2],point2d(r))
3334    ) : type == "rgn_isect" || type == "rgn_extent"? ( //path
3335        let(
3336            mm = pointlist_bounds(flatten(geom[1])),
3337            delt = mm[1]-mm[0]
3338        ) [delt.x, delt.y]
3339    ) :
3340    assert(false, "Unknown attachment geometry type.");
3341
3342
3343
3344/// Internal Function: _attach_geom_edge_path()
3345/// Usage:
3346///   angle = _attach_geom_edge_path(geom, edge);
3347/// Topics: Attachments
3348/// See Also: reorient(), attachable()
3349/// Description:
3350///   Returns the path and post-transform matrix of the indicated edge.
3351///   If the edge is invalid for the geometry, returns `undef`.
3352function _attach_geom_edge_path(geom, edge) =
3353    assert(is_vector(edge),str("Invalid edge: edge=",edge))
3354    let(
3355        type = geom[0],
3356        cp = _get_cp(geom),
3357        offset_raw = select(geom,-2),
3358        offset = [for (i=[0:2]) edge[i]==0? 0 : offset_raw[i]],  // prevents bad centering.
3359        edge = point3d(edge)
3360    )
3361    type == "prismoid"? ( //size, size2, shift, axis
3362        let(all_comps_good = [for (c=edge) if (c!=sign(c)) 1]==[])
3363        assert(all_comps_good, "All components of an edge for a cuboid/prismoid must be -1, 0, or 1")
3364        let(edge_good = len([for (c=edge) if(c) 1])==2)
3365        assert(edge_good, "Invalid edge.")
3366        let(
3367            size = geom[1],
3368            size2 = geom[2],
3369            shift = point2d(geom[3]),
3370            axis = point3d(geom[4]),
3371            edge = rot(from=axis, to=UP, p=edge),
3372            offset = rot(from=axis, to=UP, p=offset),
3373            h = size.z,
3374            cpos = function(vec) let(
3375                        u = (vec.z + 1) / 2,
3376                        siz = lerp(point2d(size), size2, u) / 2,
3377                        z = vec.z * h / 2,
3378                        pos = point3d(v_mul(siz, point2d(vec)) + shift * u, z)
3379                    ) pos,
3380            ep1 = cpos([for (c=edge) c? c : -1]),
3381            ep2 = cpos([for (c=edge) c? c :  1]),
3382            cp = (ep1 + ep2) / 2,
3383            axy = point2d(edge),
3384            bot = point3d(v_mul(point2d(size )/2, axy), -h/2),
3385            top = point3d(v_mul(point2d(size2)/2, axy) + shift, h/2),
3386            xang = atan2(h,(top-bot).x),
3387            yang = atan2(h,(top-bot).y),
3388            vecs = [
3389                if (edge.x) yrot(90-xang, p=sign(axy.x)*RIGHT),
3390                if (edge.y) xrot(yang-90, p=sign(axy.y)*BACK),
3391                if (edge.z) [0,0,sign(edge.z)]
3392            ], 
3393            segvec = cross(unit(vecs[1]), unit(vecs[0])),
3394            seglen = norm(ep2 - ep1),
3395            path = [
3396                cp - segvec * seglen/2,
3397                cp + segvec * seglen/2
3398            ],
3399            m = rot(from=UP,to=axis) * move(offset)
3400        ) [path, [vecs], m]
3401    ) : type == "conoid"? ( //r1, r2, l, shift, axis
3402        assert(edge.z && edge.z == sign(edge.z), "The Z component of an edge for a cylinder/cone must be -1 or 1")
3403        let(
3404            rr1 = geom[1],
3405            rr2 = geom[2],
3406            l = geom[3],
3407            shift = point2d(geom[4]),
3408            axis = point3d(geom[5]),
3409            r1 = is_num(rr1)? [rr1,rr1] : point2d(rr1),
3410            r2 = is_num(rr2)? [rr2,rr2] : point2d(rr2),
3411            edge = rot(from=axis, to=UP, p=edge),
3412            offset = rot(from=axis, to=UP, p=offset),
3413            maxr = max([each r1, each r2]),
3414            sides = segs(maxr),
3415            top = path3d(move(shift, p=ellipse(r=r2, $fn=sides)), l/2),
3416            bot = path3d(ellipse(r=r1, $fn=sides), -l/2),
3417            path = edge.z < 0 ? bot : top,
3418            path2 = edge.z < 0 ? top : bot,
3419            zed = edge.z<0? [0,0,-l/2] : point3d(shift,l/2),
3420            vecs = [
3421                for (i = idx(top)) let(
3422                    pt1 = (path[i] + select(path,i+1)) /2,
3423                    pt2 = (path2[i] + select(path2,i+1)) /2,
3424                    v1 = unit(zed - pt1),
3425                    v2 = unit(pt2 - pt1),
3426                    v3 = unit(cross(v1,v2)),
3427                    v4 = cross(v3,v2),
3428                    v5 = cross(v1,v3)
3429                ) [v4, v5]
3430            ],
3431            m = rot(from=UP,to=axis) * move(offset)
3432        ) edge.z>0
3433          ? [reverse(list_wrap(path)), reverse(vecs), m]
3434          : [list_wrap(path), vecs, m]
3435    ) : undef;
3436
3437
3438/// Internal Function: _attach_transform()
3439/// Usage: To Get a Transformation Matrix
3440///   mat = _attach_transform(anchor, spin, orient, geom);
3441/// Usage: To Transform Points, Paths, Patches, or VNFs
3442///   new_p = _attach_transform(anchor, spin, orient, geom, p);
3443/// Topics: Attachments
3444/// See Also: reorient(), attachable()
3445/// Description:
3446///   Returns the affine3d transformation matrix needed to `anchor`, `spin`, and `orient`
3447///   the given geometry `geom` shape into position.
3448/// Arguments:
3449///   anchor = Anchor point to translate to the origin `[0,0,0]`.  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
3450///   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3451///   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3452///   geom = The geometry description of the shape.
3453///   p = If given as a VNF, path, or point, applies the affine3d transformation matrix to it and returns the result.
3454
3455function _attach_transform(anchor, spin, orient, geom, p) =
3456    assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
3457    assert(is_undef(spin)   || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
3458    assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient))
3459    let(
3460        anchor = default(anchor, CENTER),
3461        spin   = default(spin,   0),
3462        orient = default(orient, UP),
3463        two_d = _attach_geom_2d(geom),
3464        m = ($attach_to != undef)? (
3465            let(
3466                anch = _find_anchor($attach_to, geom),
3467                pos = is_undef($anchor_override) ? anch[1]
3468                    : _find_anchor(_make_anchor_legal($anchor_override,geom),geom)[1]
3469            )
3470            two_d?
3471                assert(is_num(spin))
3472                /*affine3d_zrot(spin) * */
3473                    rot(to=FWD, from=point3d(anch[2])) 
3474                   * affine3d_translate(point3d(-pos))
3475            :
3476                assert(is_num(spin) || is_vector(spin,3))
3477                let(
3478                    ang = vector_angle(anch[2], DOWN),
3479                    axis = vector_axis(anch[2], DOWN),
3480                    ang2 = (anch[2]==UP || anch[2]==DOWN)? 0 : 180-anch[3],
3481                    axis2 = rot(p=axis,[0,0,ang2])
3482                )
3483                affine3d_rot_by_axis(axis2,ang)
3484                   * (is_num(spin)? affine3d_zrot(ang2+spin)
3485                                  : affine3d_zrot(spin.z) * affine3d_yrot(spin.y) * affine3d_xrot(spin.x) 
3486                                     * affine3d_zrot(ang2))
3487                   * affine3d_translate(point3d(-pos))
3488        ) : (
3489            let(
3490                anchor = is_undef($attach_alignment) ? anchor
3491                       : two_d? _make_anchor_legal(zrot(-spin,$attach_alignment),geom)
3492                       : _make_anchor_legal(rot(spin, from=UP,to=orient,reverse=true,p=$attach_alignment),geom),
3493                pos = _find_anchor(anchor, geom)[1]
3494            )
3495            two_d? 
3496                assert(is_num(spin))
3497                affine3d_zrot(spin) * affine3d_translate(point3d(-pos))
3498            :
3499                assert(is_num(spin) || is_vector(spin,3))
3500                let(
3501                    axis = vector_axis(UP,orient),
3502                    ang = vector_angle(UP,orient)
3503                )
3504                affine3d_rot_by_axis(axis,ang) 
3505                    * ( is_num(spin)? affine3d_zrot(spin)  
3506                                    : affine3d_zrot(spin.z) * affine3d_yrot(spin.y) * affine3d_xrot(spin.x))
3507                    * affine3d_translate(point3d(-pos))
3508        )
3509    )
3510    is_undef(p)? m
3511  : is_vnf(p) && p==EMPTY_VNF? p 
3512  : apply(m, p);
3513
3514
3515function _get_cp(geom) =
3516    let(cp=select(geom,-3))
3517    is_vector(cp) ? cp
3518  : let(
3519        type = in_list(geom[0],["vnf_extent","vnf_isect"]) ? "vnf"
3520             : in_list(geom[0],["rgn_extent","rgn_isect"]) ? "path"
3521             : in_list(geom[0],["extrusion_extent","extrusion_isect"]) ? "xpath"
3522             : "other"
3523    )
3524    assert(type!="other", "Invalid cp value")
3525    cp=="centroid" ? (
3526       type=="vnf" && (len(geom[1][0])==0 || len(geom[1][1])==0) ? [0,0,0] :
3527       [each centroid(geom[1]), if (type=="xpath") 0]
3528    )
3529  : let(points = type=="vnf"?geom[1][0]:flatten(force_region(geom[1])))
3530    cp=="mean" ? [each mean(points), if (type=="xpath") 0]
3531  : cp=="box" ?[each  mean(pointlist_bounds(points)), if (type=="xpath") 0]
3532  : assert(false,"Invalid cp specification");
3533
3534
3535function _get_cp(geom) =
3536    let(cp=select(geom,-3))
3537    is_vector(cp) ? cp
3538  : let(
3539        is_vnf = in_list(geom[0],["vnf_extent","vnf_isect"])
3540    )
3541    cp == "centroid" ? (
3542       is_vnf && len(geom[1][1])==0
3543          ? [0,0,0]
3544          : centroid(geom[1])
3545    )
3546  : let(points = is_vnf?geom[1][0]:flatten(force_region(geom[1])))
3547    cp=="mean" ? mean(points)
3548  : cp=="box" ? mean(pointlist_bounds(points))
3549  : assert(false,"Invalid cp specification");
3550
3551
3552
3553function _force_anchor_2d(anchor) =
3554  is_undef(anchor) || len(anchor)==2 ? anchor :
3555  assert(anchor.y==0 || anchor.z==0, "Anchor for a 2D shape cannot be fully 3D.  It must have either Y or Z component equal to zero.")
3556  anchor.y==0 ? [anchor.x,anchor.z] : point2d(anchor);
3557
3558
3559/// Internal Function: _find_anchor()
3560/// Usage:
3561///   anchorinfo = _find_anchor(anchor, geom);
3562/// Topics: Attachments
3563/// See Also: reorient(), attachable()
3564/// Description:
3565///   Calculates the anchor data for the given `anchor` vector or name, in the given attachment
3566///   geometry.  Returns `[ANCHOR, POS, VEC, ANG]` where `ANCHOR` is the requested anchorname
3567///   or vector, `POS` is the anchor position, `VEC` is the direction vector of the anchor, and
3568///   `ANG` is the angle to align with around the rotation axis of th anchor direction vector.
3569/// Arguments:
3570///   anchor = Vector or named anchor string.
3571///   geom = The geometry description of the shape.
3572function _find_anchor(anchor, geom) =
3573    is_string(anchor)? (
3574          anchor=="origin"? [anchor, CENTER, UP, 0]    // Ok that this returns 3d anchor in the 2d case?
3575        : let(
3576              anchors = last(geom),
3577              found = search([anchor], anchors, num_returns_per_match=1)[0]
3578          )
3579          assert(found!=[], str("Unknown anchor: ",anchor))
3580          anchors[found]
3581    ) :
3582    let(
3583        cp = _get_cp(geom),
3584        offset_raw = select(geom,-2),
3585        offset = [for (i=[0:2]) anchor[i]==0? 0 : offset_raw[i]],  // prevents bad centering.
3586        type = geom[0]
3587    )
3588    assert(is_vector(anchor),str("Invalid anchor: anchor=",anchor))
3589    let(
3590        anchor = point3d(anchor),
3591        oang = (
3592            approx(point2d(anchor), [0,0])? 0 :
3593            atan2(anchor.y, anchor.x)+90
3594        )
3595    )
3596    type == "prismoid"? ( //size, size2, shift, axis
3597        let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[])
3598        assert(all_comps_good, "All components of an anchor for a cuboid/prismoid must be -1, 0, or 1")
3599        let(
3600            size=geom[1], size2=geom[2],
3601            shift=point2d(geom[3]), axis=point3d(geom[4]),
3602            override = geom[5](anchor)
3603        )
3604        let(
3605            size = [for (c = size) max(0,c)],
3606            size2 = [for (c = size2) max(0,c)],
3607            anch = rot(from=axis, to=UP, p=anchor),
3608            offset = rot(from=axis, to=UP, p=offset),
3609            h = size.z,
3610            u = (anch.z + 1) / 2,  // u is one of 0, 0.5, or 1
3611            axy = point2d(anch),
3612            bot = point3d(v_mul(point2d(size )/2, axy), -h/2),
3613            top = point3d(v_mul(point2d(size2)/2, axy) + shift, h/2),
3614            pos = point3d(cp) + lerp(bot,top,u) + offset,
3615            vecs = anchor==CENTER? [UP]
3616              : [
3617                    if (anch.x!=0) unit(rot(from=UP, to=[(top-bot).x,0,max(0.01,h)], p=[axy.x,0,0]), UP),
3618                    if (anch.y!=0) unit(rot(from=UP, to=[0,(top-bot).y,max(0.01,h)], p=[0,axy.y,0]), UP),
3619                    if (anch.z!=0) unit([0,0,anch.z],UP)
3620                ],
3621            vec2 = anchor==CENTER? UP
3622              : len(vecs)==1? unit(vecs[0],UP)
3623              : len(vecs)==2? vector_bisect(vecs[0],vecs[1])
3624              : let(
3625                    v1 = vector_bisect(vecs[0],vecs[2]),
3626                    v2 = vector_bisect(vecs[1],vecs[2]),
3627                    p1 = plane_from_normal(yrot(90,p=v1)),
3628                    p2 = plane_from_normal(xrot(-90,p=v2)),
3629                    line = plane_intersection(p1,p2),
3630                    v3 = unit(line[1]-line[0],UP) * anch.z
3631                )
3632                unit(v3,UP),
3633            vec = default(override[1],rot(from=UP, to=axis, p=vec2)),
3634            pos2 = default(override[0],rot(from=UP, to=axis, p=pos))
3635        ) [anchor, pos2, vec, default(override[2],oang)]
3636    ) : type == "conoid"? ( //r1, r2, l, shift
3637        assert(anchor.z == sign(anchor.z), "The Z component of an anchor for a cylinder/cone must be -1, 0, or 1")
3638        let(
3639            rr1=geom[1], rr2=geom[2], l=geom[3],
3640            shift=point2d(geom[4]), axis=point3d(geom[5]),
3641            r1 = is_num(rr1)? [rr1,rr1] : point2d(rr1),
3642            r2 = is_num(rr2)? [rr2,rr2] : point2d(rr2),
3643            anch = rot(from=axis, to=UP, p=anchor),
3644            offset = rot(from=axis, to=UP, p=offset),
3645            u = (anch.z+1)/2,
3646            axy = unit(point2d(anch),[0,0]),
3647            bot = point3d(v_mul(r1,axy), -l/2),
3648            top = point3d(v_mul(r2,axy)+shift, l/2),
3649            pos = point3d(cp) + lerp(bot,top,u) + offset,
3650            sidevec = rot(from=UP, to=top==bot?UP:top-bot, p=point3d(axy)),
3651            vvec = anch==CENTER? UP : unit([0,0,anch.z],UP),
3652            vec = anch==CENTER? CENTER :
3653                approx(axy,[0,0])? unit(anch,UP) :
3654                approx(anch.z,0)? sidevec :
3655                unit((sidevec+vvec)/2,UP),
3656            pos2 = rot(from=UP, to=axis, p=pos),
3657            vec2 = anch==CENTER? UP : rot(from=UP, to=axis, p=vec)
3658        ) [anchor, pos2, vec2, oang]
3659    ) : type == "point"? (
3660        let(
3661            anchor = unit(point3d(anchor),CENTER),
3662            pos = point3d(cp) + point3d(offset),
3663            vec = unit(anchor,UP)
3664        ) [anchor, pos, vec, oang]
3665    ) : type == "spheroid"? ( //r
3666        let(
3667            rr = geom[1],
3668            r = is_num(rr)? [rr,rr,rr] : point3d(rr),
3669            anchor = unit(point3d(anchor),CENTER),
3670            pos = point3d(cp) + v_mul(r,anchor) + point3d(offset),
3671            vec = unit(v_mul(r,anchor),UP)
3672        ) [anchor, pos, vec, oang]
3673    ) : type == "vnf_isect"? ( //vnf
3674        let( vnf=geom[1] )
3675        approx(anchor,CTR)? [anchor, cp, UP, 0] :      // CENTER anchors anchor on cp, "origin" anchors on [0,0]
3676        vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor), 0] :
3677        let(
3678            eps = 1/2048,
3679            points = vnf[0],
3680            faces = vnf[1],
3681            rpts = apply(rot(from=anchor, to=RIGHT) * move(-cp), points),
3682            hits = [
3683                for (face = faces)
3684                    let(
3685                        verts = select(rpts, face),
3686                        ys = column(verts,1),
3687                        zs = column(verts,2)
3688                    )
3689                    if (max(ys) >= -eps && max(zs) >= -eps &&
3690                        min(ys) <=  eps &&  min(zs) <=  eps)
3691                        let(
3692                            poly = select(points, face),
3693                            isect = polygon_line_intersection(poly, [cp,cp+anchor], eps=eps),
3694                            ptlist = is_undef(isect) ? [] :
3695                                     is_vector(isect) ? [isect]
3696                                                      : flatten(isect),   // parallel to a face
3697                            n = len(ptlist)>0 ? polygon_normal(poly) : undef
3698                        )
3699                        for(pt=ptlist) [anchor * (pt-cp), n, pt]
3700            ]
3701        )
3702        assert(len(hits)>0, "Anchor vector does not intersect with the shape.  Attachment failed.")
3703        let(
3704            furthest = max_index(column(hits,0)),
3705            dist = hits[furthest][0],
3706            pos = hits[furthest][2],
3707            hitnorms = [for (hit = hits) if (approx(hit[0],dist,eps=eps)) hit[1]],
3708            unorms = [
3709                      for (i = idx(hitnorms))
3710                          let(
3711                              thisnorm = hitnorms[i],
3712                              isdup = [
3713                                       for (j = [i+1:1:len(hitnorms)-1])
3714                                           if (approx(thisnorm, hitnorms[j])) 1
3715                                      ] != []
3716                          )
3717                          if (!isdup) thisnorm
3718                     ],
3719            n = unit(sum(unorms)),
3720            oang = approx(point2d(n), [0,0])? 0 : atan2(n.y, n.x) + 90
3721        )
3722        [anchor, pos, n, oang]
3723    ) : type == "vnf_extent"? ( //vnf
3724        let( vnf=geom[1] )
3725        approx(anchor,CTR)? [anchor, cp, UP, 0] :      // CENTER anchors anchor on cp, "origin" anchors on [0,0]
3726        vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor,UP), 0] :
3727        let(
3728            rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]),
3729            maxx = max(column(rpts,0)),
3730            idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i],
3731            avep = sum(select(rpts,idxs))/len(idxs),
3732            mpt = approx(point2d(anchor),[0,0])? [maxx,0,0] : avep,
3733            pos = point3d(cp) + rot(from=RIGHT, to=anchor, p=mpt)
3734        ) [anchor, pos, anchor, oang]
3735    ) : type == "trapezoid"? ( //size, size2, shift, override
3736        let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[])
3737        assert(all_comps_good, "All components of an anchor for a rectangle/trapezoid must be -1, 0, or 1")
3738        let(
3739            anchor=_force_anchor_2d(anchor),
3740            size=geom[1], size2=geom[2], shift=geom[3],
3741            u = (anchor.y+1)/2,  // 0<=u<=1
3742            frpt = [size.x/2*anchor.x, -size.y/2],
3743            bkpt = [size2/2*anchor.x+shift, size.y/2],
3744            override = geom[4](anchor),
3745            pos = override[0] != undef? override[0] :
3746                point2d(cp) + lerp(frpt, bkpt, u) + point2d(offset),
3747            svec = approx(bkpt,frpt)? [anchor.x,0,0] :
3748                point3d(line_normal(bkpt,frpt)*anchor.x),
3749            vec = is_def(override[1]) ? override[1]
3750                : anchor.y == 0? ( anchor.x == 0? BACK : svec )
3751                : anchor.x == 0? [0,anchor.y,0]
3752                : unit((svec + [0,anchor.y,0]) / 2, [0,anchor.y,0])
3753        ) [anchor, pos, vec, 0]
3754    ) : type == "ellipse"? ( //r
3755        let(
3756            anchor = unit(_force_anchor_2d(anchor),[0,0]),
3757            r = force_list(geom[1],2),
3758            pos = approx(anchor.x,0)
3759                ? [0,sign(anchor.y)*r.y]
3760                : let(
3761                       m = anchor.y/anchor.x,
3762                       px = approx(min(r),0)? 0 :
3763                           sign(anchor.x) * sqrt(1/(1/sqr(r.x) + m*m/sqr(r.y)))
3764                  )
3765                  [px,m*px],
3766            vec = approx(min(r),0)? (approx(norm(anchor),0)? BACK : anchor) :
3767                unit([r.y/r.x*pos.x, r.x/r.y*pos.y],BACK)
3768        ) [anchor, point2d(cp+offset)+pos, vec, 0]
3769    ) : type == "rgn_isect"? ( //region
3770        let(
3771            anchor = _force_anchor_2d(anchor),
3772            rgn = force_region(move(-point2d(cp), p=geom[1]))
3773        )
3774        approx(anchor,[0,0])? [anchor, cp, BACK, 0] :     // CENTER anchors anchor on cp, "origin" anchors on [0,0]
3775        let(
3776            isects = [
3777                for (path=rgn, t=triplet(path,true)) let(
3778                    seg1 = [t[0],t[1]],
3779                    seg2 = [t[1],t[2]],
3780                    isect = line_intersection([[0,0],anchor], seg1, RAY, SEGMENT),
3781                    n = is_undef(isect)? [0,1] :
3782                        !approx(isect, t[1])? line_normal(seg1) :
3783                        unit((line_normal(seg1)+line_normal(seg2))/2,[0,1]),
3784                    n2 = vector_angle(anchor,n)>90? -n : n
3785                )
3786                if(!is_undef(isect) && !approx(isect,t[0])) [norm(isect), isect, n2]
3787            ]
3788        )
3789        assert(len(isects)>0, "Anchor vector does not intersect with the shape.  Attachment failed.")
3790        let(
3791            maxidx = max_index(column(isects,0)),
3792            isect = isects[maxidx],
3793            pos = point2d(cp) + isect[1],
3794            vec = unit(isect[2],[0,1])
3795        ) [anchor, pos, vec, 0]
3796    ) : type == "rgn_extent"? ( //region
3797        let( anchor = _force_anchor_2d(anchor) )
3798        approx(anchor,[0,0])? [anchor, cp, BACK, 0] :   // CENTER anchors anchor on cp, "origin" anchors on [0,0]
3799        let(
3800            rgn = force_region(geom[1]),
3801            rpts = rot(from=anchor, to=RIGHT, p=flatten(rgn)),
3802            maxx = max(column(rpts,0)),
3803            ys = [for (pt=rpts) if (approx(pt.x, maxx)) pt.y],
3804            midy = (min(ys)+max(ys))/2,
3805            pos = rot(from=RIGHT, to=anchor, p=[maxx,midy])
3806        ) [anchor, pos, unit(anchor,BACK), 0]
3807    ) : type=="extrusion_extent" || type=="extrusion_isect" ? (  // extruded region
3808        assert(in_list(anchor.z,[-1,0,1]), "The Z component of an anchor for an extruded 2D shape must be -1, 0, or 1.")
3809        let(
3810            anchor_xy = point2d(anchor),
3811            rgn = geom[1],
3812            L = geom[2],
3813            twist = geom[3],
3814            scale = geom[4],
3815            shift = geom[5],
3816            u = (anchor.z + 1) / 2,
3817            shmat = move(lerp([0,0], shift, u)),
3818            scmat = scale(lerp([1,1], scale, u)),
3819            twmat = zrot(lerp(0, -twist, u)),
3820            mat = shmat * scmat * twmat
3821        )
3822        approx(anchor_xy,[0,0]) ? [anchor, apply(mat, point3d(cp,anchor.z*L/2)), unit(anchor, UP), oang] :
3823        let(
3824            newrgn = apply(mat, rgn),
3825            newgeom = attach_geom(two_d=true, region=newrgn, extent=type=="extrusion_extent", cp=cp),
3826            topmat = anchor.z!=0 ? []
3827                   : move(shift)*scale(scale)*zrot(-twist),
3828            topgeom = anchor.z!=0? []
3829                    : attach_geom(two_d=true, region=apply(topmat,rgn), extent=type=="extrusion_extent", cp=cp),
3830            top2d =  anchor.z!=0? []
3831                  : _find_anchor(anchor_xy, topgeom),
3832            result2d = _find_anchor(anchor_xy, newgeom),
3833            pos = point3d(result2d[1], anchor.z*L/2),
3834            vec = anchor.z==0? rot(from=UP,to=point3d(top2d[1],L/2)-point3d(result2d[1]),p=point3d(result2d[2]))
3835                : unit(point3d(result2d[2], anchor.z),UP),
3836            oang = atan2(vec.y,vec.x) + 90
3837        )
3838        [anchor, pos, vec, oang]
3839    ) :
3840    assert(false, "Unknown attachment geometry type.");
3841
3842
3843/// Internal Function: _is_shown()
3844/// Usage:
3845///   bool = _is_shown();
3846/// Topics: Attachments
3847/// See Also: reorient(), attachable()
3848/// Description:
3849///   Returns true if objects should currently be shown based on the tag settings.
3850function _is_shown() =
3851    assert(is_list($tags_shown) || $tags_shown=="ALL")
3852    assert(is_list($tags_hidden))
3853    let(
3854        dummy=is_undef($tags) ? 0 : echo("Use tag() instead of $tags for specifying an object's tag."),
3855        $tag = default($tag,$tags)
3856    )
3857    assert(is_string($tag), str("Tag value (",$tag,") is not a string"))
3858    assert(undef==str_find($tag," "),str("Tag string \"",$tag,"\" contains a space, which is not allowed"))
3859    let(
3860        shown  = $tags_shown=="ALL" || in_list($tag,$tags_shown),
3861        hidden = in_list($tag, $tags_hidden)
3862    )
3863    shown && !hidden;
3864
3865
3866// Section: Visualizing Anchors
3867
3868/// Internal Function: _standard_anchors()
3869/// Usage:
3870///   anchs = _standard_anchors([two_d]);
3871/// Description:
3872///   Return the vectors for all standard anchors.
3873/// Arguments:
3874///   two_d = If true, returns only the anchors where the Z component is 0.  Default: false
3875function _standard_anchors(two_d=false) = [
3876    for (
3877        zv = [
3878            if (!two_d) TOP,
3879            CENTER,
3880            if (!two_d) BOTTOM
3881        ],
3882        yv = [FRONT, CENTER, BACK],
3883        xv = [LEFT, CENTER, RIGHT]
3884    ) xv+yv+zv
3885];
3886
3887
3888
3889// Module: show_anchors()
3890// Synopsis: Shows anchors for the parent object.
3891// SynTags: Geom
3892// Topics: Attachments
3893// See Also: expose_anchors(), anchor_arrow(), anchor_arrow2d(), frame_ref()
3894// Usage:
3895//   PARENT() show_anchors([s], [std=], [custom=]);
3896// Description:
3897//   Show all standard anchors for the parent object.
3898// Arguments:
3899//   s = Length of anchor arrows.
3900//   ---
3901//   std = If true show standard anchors.  Default: true
3902//   custom = If true show named anchors.  Default: true
3903// Example(FlatSpin,VPD=333):
3904//   cube(50, center=true) show_anchors();
3905module show_anchors(s=10, std=true, custom=true) {
3906    check = assert($parent_geom != undef);
3907    two_d = _attach_geom_2d($parent_geom);
3908    if (std) {
3909        for (anchor=_standard_anchors(two_d=two_d)) {
3910            if(two_d) {
3911                attach(anchor) anchor_arrow2d(s);
3912            } else {
3913                attach(anchor) anchor_arrow(s);
3914            }
3915        }
3916    }
3917    if (custom) {
3918        for (anchor=last($parent_geom)) {
3919            attach(anchor[0]) {
3920                if(two_d) {
3921                    anchor_arrow2d(s, color="cyan");
3922                } else {
3923                    anchor_arrow(s, color="cyan");
3924                }
3925                color("black")
3926                tag("anchor-arrow") {
3927                    xrot(two_d? 0 : 90) {
3928                        back(s/3) {
3929                            yrot_copies(n=2)
3930                            up(two_d? 0.51 : s/30) {
3931                                linear_extrude(height=0.01, convexity=12, center=true) {
3932                                    text(text=anchor[0], size=s/4, halign="center", valign="center", font="Helvetica", $fn=36);
3933                                }
3934                            }
3935                        }
3936                    }
3937                }
3938                color([1, 1, 1, 1])
3939                tag("anchor-arrow") {
3940                    xrot(two_d? 0 : 90) {
3941                        back(s/3) {
3942                             cube([s/4.5*len(anchor[0]), s/3, 0.01], center=true);
3943                        }
3944                   }
3945                }
3946            }
3947        }
3948    }
3949    children();
3950}
3951
3952
3953// Module: anchor_arrow()
3954// Synopsis: Shows a 3d anchor orientation arrow.
3955// SynTags: Geom
3956// Topics: Attachments
3957// See Also: anchor_arrow2d(), show_anchors(), expose_anchors(), frame_ref(), generic_airplane()
3958// Usage:
3959//   anchor_arrow([s], [color], [flag], [anchor=], [orient=], [spin=]) [ATTACHMENTS];
3960// Description:
3961//   Show an anchor orientation arrow.  By default, tagged with the name "anchor-arrow".
3962// Arguments:
3963//   s = Length of the arrows.  Default: `10`
3964//   color = Color of the arrow.  Default: `[0.333, 0.333, 1]`
3965//   flag = If true, draw the orientation flag on the arrowhead.  Default: true
3966//   ---
3967//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
3968//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3969//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3970// Example:
3971//   anchor_arrow(s=20);
3972module anchor_arrow(s=10, color=[0.333,0.333,1], flag=true, $tag="anchor-arrow", $fn=12, anchor=BOT, spin=0, orient=UP) {
3973    attachable(anchor,spin,orient, r=s/6, l=s) {
3974        down(s/2)
3975        recolor("gray") spheroid(d=s/6) {
3976            attach(CENTER,BOT) recolor(color) cyl(h=s*2/3, d=s/15) {
3977                attach(TOP,BOT) cyl(h=s/3, d1=s/5, d2=0) {
3978                    if(flag) {
3979                        position(BOT)
3980                            recolor([1,0.5,0.5])
3981                                cuboid([s/100, s/6, s/4], anchor=FRONT+BOT);
3982                    }
3983                }
3984            }
3985        }
3986        children();
3987    }
3988}
3989
3990
3991
3992// Module: anchor_arrow2d()
3993// Synopsis: Shows a 2d anchor orientation arrow.
3994// SynTags: Geom
3995// Topics: Attachments
3996// See Also: anchor_arrow(), show_anchors(), expose_anchors(), frame_ref()
3997// Usage:
3998//   anchor_arrow2d([s], [color]);
3999// Description:
4000//   Show an anchor orientation arrow.
4001// Arguments:
4002//   s = Length of the arrows.
4003//   color = Color of the arrow.
4004// Example:
4005//   anchor_arrow2d(s=20);
4006module anchor_arrow2d(s=15, color=[0.333,0.333,1], $tag="anchor-arrow") {
4007    color(color) stroke([[0,0],[0,s]], width=s/10, endcap1="butt", endcap2="arrow2");
4008}
4009
4010
4011
4012// Module: expose_anchors()
4013// Synopsis: Used to show a transparent object with solid color anchor arrows.
4014// Topics: Attachments
4015// See Also: anchor_arrow2d(), show_anchors(), show_anchors(), frame_ref()
4016// Usage:
4017//   expose_anchors(opacity) {child1() show_anchors(); child2() show_anchors(); ...}
4018// Description:
4019//   Used in combination with show_anchors() to display an object in transparent gray with its anchors in solid color.
4020//   Children will appear transparent and any anchor arrows drawn with will appear in solid color.
4021// Arguments:
4022//   opacity = The opacity of the children.  0.0 is invisible, 1.0 is opaque.  Default: 0.2
4023// Example(FlatSpin,VPD=333):
4024//   expose_anchors() cube(50, center=true) show_anchors();
4025module expose_anchors(opacity=0.2) {
4026    show_only("anchor-arrow")
4027        children();
4028    hide("anchor-arrow")
4029        color(is_undef($color) || $color=="default" ? [0,0,0] :
4030              is_string($color) ? $color
4031                                : point3d($color),
4032              opacity)
4033            children();
4034}
4035
4036
4037
4038// Module: show_transform_list()
4039// Synopsis: Shows a list of transforms and how they connect.
4040// SynTags: Geom
4041// Topics: Attachments
4042// See Also: generic_airplane(), anchor_arrow(), show_anchors(), expose_anchors(), frame_ref()
4043// Usage:
4044//   show_transform_list(tlist, [s]);
4045//   show_transform_list(tlist) {CHILDREN};
4046// Description:
4047//   Given a list of transformation matrices, shows the position and orientation of each one.
4048//   A line is drawn from each transform position to the next one, and an orientation indicator is
4049//   shown at each position.  If a child is passed, that child will be used as the orientation indicator.
4050//   By default, a {{generic_airplane()}} is used as the orientation indicator.
4051// Arguments:
4052//   s = Length of the {{generic_airplane()}}.  Default: 5
4053// Example:
4054//   tlist = [
4055//       zrot(90),
4056//       zrot(90) * fwd(30) * zrot(30),
4057//       zrot(90) * fwd(30) * zrot(30) *
4058//           fwd(35) * xrot(-30),
4059//       zrot(90) * fwd(30) * zrot(30) *
4060//           fwd(35) * xrot(-30) * fwd(40) * yrot(15),
4061//   ];
4062//   show_transform_list(tlist, s=20);
4063// Example:
4064//   tlist = [
4065//       zrot(90),
4066//       zrot(90) * fwd(30) * zrot(30),
4067//       zrot(90) * fwd(30) * zrot(30) *
4068//           fwd(35) * xrot(-30),
4069//       zrot(90) * fwd(30) * zrot(30) *
4070//           fwd(35) * xrot(-30) * fwd(40) * yrot(15),
4071//   ];
4072//   show_transform_list(tlist) frame_ref();
4073module show_transform_list(tlist, s=5) {
4074    path = [for (m = tlist) apply(m, [0,0,0])];
4075    stroke(path, width=s*0.03);
4076    for (m = tlist) {
4077        multmatrix(m) {
4078            if ($children>0) children();
4079            else generic_airplane(s=s);
4080        }
4081    }
4082}
4083
4084
4085// Module: generic_airplane()
4086// Synopsis: Shows a generic airplane shape, useful for viewing orientations.
4087// SynTags: Geom
4088// Topics: Attachments
4089// See Also: anchor_arrow(), show_anchors(), expose_anchors(), frame_ref()
4090// Usage:
4091//   generic_airplane([s]);
4092// Description:
4093//   Creates a generic airplane shape.  This can be useful for viewing the orientation of 3D transforms.
4094// Arguments:
4095//   s = Length of the airplane.  Default: 5
4096// Example:
4097//   generic_airplane(s=20);
4098module generic_airplane(s=5) {
4099    $fn = max(segs(0.05*s), 12);
4100    color("#ddd")
4101    fwd(s*0.05)
4102    ycyl(l=0.7*s, d=0.1*s) {
4103        attach(FWD) top_half(s=s) zscale(2) sphere(d=0.1*s);
4104        attach(BACK,FWD) ycyl(l=0.2*s, d1=0.1*s, d2=0.05*s) {
4105            yrot_copies([-90,0,90])
4106                prismoid(s*[0.01,0.2], s*[0.01,0.05],
4107                    h=0.2*s, shift=s*[0,0.15], anchor=BOT);
4108        }
4109        yrot_copies([-90,90])
4110            prismoid(s*[0.01,0.2], s*[0.01,0.05],
4111                h=0.5*s, shift=s*[0,0.15], anchor=BOT);
4112    }
4113    color("#777") zcopies(0.1*s) sphere(d=0.02*s);
4114    back(0.09*s) {
4115        color("#f00") right(0.46*s) sphere(d=0.04*s);
4116        color("#0f0") left(0.46*s) sphere(d=0.04*s);
4117    }
4118}
4119
4120
4121
4122// Module: frame_ref()
4123// Synopsis: Shows axis orientation arrows.
4124// SynTags: Geom
4125// Topics: Attachments
4126// See Also: anchor_arrow(), anchor_arrow2d(), show_anchors(), expose_anchors()
4127// Usage:
4128//   frame_ref(s, opacity);
4129// Description:
4130//   Displays X,Y,Z axis arrows in red, green, and blue respectively.
4131// Arguments:
4132//   s = Length of the arrows.
4133//   opacity = The opacity of the arrows.  0.0 is invisible, 1.0 is opaque.  Default: 1.0
4134// Examples:
4135//   frame_ref(25);
4136//   frame_ref(30, opacity=0.5);
4137module frame_ref(s=15, opacity=1) {
4138    cube(0.01, center=true) {
4139        attach([1,0,0]) anchor_arrow(s=s, flag=false, color=[1.0, 0.3, 0.3, opacity]);
4140        attach([0,1,0]) anchor_arrow(s=s, flag=false, color=[0.3, 1.0, 0.3, opacity]);
4141        attach([0,0,1]) anchor_arrow(s=s, flag=false, color=[0.3, 0.3, 1.0, opacity]);
4142        children();
4143    }
4144}
4145
4146
4147////////////////////////////////////////////////////////////////////////////////////////////////////
4148////////////////////////////////////////////////////////////////////////////////////////////////////
4149////////////////////////////////////////////////////////////////////////////////////////////////////
4150////////////////////////////////////////////////////////////////////////////////////////////////////
4151///
4152/// Code after this is internal code for managing edge and corner sets and for displaying
4153/// edge and corners in the docs
4154///
4155
4156module _edges_text3d(txt,size=3) {
4157    if (is_list(txt)) {
4158        for (i=idx(txt)) {
4159            down((i-len(txt)/2+0.5)*size*1.5) {
4160                _edges_text3d(txt[i], size=size);
4161            }
4162        }
4163    } else {
4164        xrot(90) color("#000")
4165        linear_extrude(height=0.1) {
4166            text(text=txt, size=size, halign="center", valign="center");
4167        }
4168    }
4169}
4170
4171
4172function _edges_vec_txt(x) = is_string(x)? str("\"", x, "\"") :
4173    assert(is_string(x) || is_vector(x,3), str(x))
4174    let(
4175        lst = concat(
4176            x.z>0? ["TOP"]   : x.z<0? ["BOT"]  : [],
4177            x.y>0? ["BACK"]  : x.y<0? ["FWD"]  : [],
4178            x.x>0? ["RIGHT"] : x.x<0? ["LEFT"] : []
4179        ),
4180        out = [
4181           for (i = idx(lst))
4182           i>0? str("+",lst[i]) : lst[i]
4183        ]
4184    ) out;
4185
4186
4187function _edges_text(edges) =
4188    is_string(edges) ? [str("\"",edges,"\"")] :
4189    edges==EDGES_NONE ? ["EDGES_NONE"] :
4190    edges==EDGES_ALL ? ["EDGES_ALL"] :
4191    _is_edge_array(edges) ? [""] :
4192    is_vector(edges,3) ? _edges_vec_txt(edges) :
4193    is_list(edges) ? let(
4194        lst = [for (x=edges) each _edges_text(x)],
4195        out = [
4196            for (i=idx(lst))
4197            str(
4198                (i==0? "[" : ""),
4199                lst[i],
4200                (i<len(lst)-1? "," : ""),
4201                (i==len(lst)-1? "]" : "")
4202            )
4203        ]
4204    ) out :
4205    [""];
4206
4207
4208
4209/// Internal Constant: EDGES_NONE
4210/// Topics: Edges
4211/// See Also: EDGES_ALL, edges()
4212/// Description:
4213///   The set of no edges.
4214/// Figure(3D):
4215///   _show_edges(edges="NONE");
4216EDGES_NONE = [[0,0,0,0], [0,0,0,0], [0,0,0,0]];
4217
4218
4219/// Internal Constant: EDGES_ALL
4220/// Topics: Edges
4221/// See Also: EDGES_NONE, edges()
4222/// Description:
4223///   The set of all edges.
4224/// Figure(3D):
4225///   _show_edges(edges="ALL");
4226EDGES_ALL = [[1,1,1,1], [1,1,1,1], [1,1,1,1]];
4227
4228
4229/// Internal Constant: EDGES_OFFSETS
4230/// Topics: Edges
4231/// See Also: EDGES_NONE, EDGES_ALL, edges()
4232/// Description:
4233///   The vectors pointing to the center of each edge of a unit sized cube.
4234///   Each item in an edge array will have a corresponding vector in this array.
4235EDGE_OFFSETS = [
4236    [
4237        [ 0,-1,-1],
4238        [ 0, 1,-1],
4239        [ 0,-1, 1],
4240        [ 0, 1, 1]
4241    ], [
4242        [-1, 0,-1],
4243        [ 1, 0,-1],
4244        [-1, 0, 1],
4245        [ 1, 0, 1]
4246    ], [
4247        [-1,-1, 0],
4248        [ 1,-1, 0],
4249        [-1, 1, 0],
4250        [ 1, 1, 0]
4251    ]
4252];
4253
4254
4255
4256/// Internal Function: _is_edge_array()
4257/// Topics: Edges, Type Checking
4258/// Usage:
4259///   bool = _is_edge_array(x);
4260/// Description:
4261///   Returns true if the given value has the form of an edge array.
4262/// Arguments:
4263///   x = The item to check the type of.
4264/// See Also: edges(), EDGES_NONE, EDGES_ALL
4265function _is_edge_array(x) = is_list(x) && is_vector(x[0]) && len(x)==3 && len(x[0])==4;
4266
4267
4268function _edge_set(v) =
4269    _is_edge_array(v)? v : [
4270    for (ax=[0:2]) [
4271        for (b=[-1,1], a=[-1,1]) let(
4272            v2=[[0,a,b],[a,0,b],[a,b,0]][ax]
4273        ) (
4274            is_string(v)? (
4275                v=="X"? (ax==0) :   // Return all X axis aligned edges.
4276                v=="Y"? (ax==1) :   // Return all Y axis aligned edges.
4277                v=="Z"? (ax==2) :   // Return all Z axis aligned edges.
4278                v=="ALL"? true :    // Return all edges.
4279                v=="NONE"? false :  // Return no edges.
4280                let(valid_values = ["X", "Y", "Z", "ALL", "NONE"])
4281                assert(
4282                    in_list(v, valid_values),
4283                    str(v, " must be a vector, edge array, or one of ", valid_values)
4284                ) v
4285            ) :
4286            let(nonz = sum(v_abs(v)))
4287            nonz==2? (v==v2) :  // Edge: return matching edge.
4288            let(
4289                matches = num_true([
4290                    for (i=[0:2]) v[i] && (v[i]==v2[i])
4291                ])
4292            )
4293            nonz==1? (matches==1) :  // Face: return surrounding edges.
4294            (matches==2)             // Corner: return touching edges.
4295        )? 1 : 0
4296    ]
4297];
4298
4299
4300/// Internal Function: _normalize_edges()
4301/// Topics: Edges
4302/// Usage:
4303///   edges = _normalize_edges(v);
4304/// Description:
4305///   Normalizes all values in an edge array to be `1`, if it was originally greater than `0`,
4306///   or `0`, if it was originally less than or equal to `0`.
4307/// See Also:  edges(), EDGES_NONE, EDGES_ALL
4308function _normalize_edges(v) = [for (ax=v) [for (edge=ax) edge>0? 1 : 0]];
4309
4310
4311
4312
4313/// Internal Function: _edges()
4314/// Topics: Edges
4315/// Usage:
4316///   edgs = _edges(v);
4317///   edgs = _edges(v, except);
4318///
4319/// Description:
4320///   Takes a list of edge set descriptors, and returns a normalized edges array
4321///   that represents all those given edges.
4322/// Arguments:
4323///   v = The edge set to include.
4324///   except = The edge set to specifically exclude, even if they are in `v`.
4325///
4326/// See Also:  EDGES_NONE, EDGES_ALL
4327///
4328function _edges(v, except=[]) =
4329    v==[] ? EDGES_NONE :
4330    (is_string(v) || is_vector(v) || _is_edge_array(v))? _edges([v], except=except) :
4331    (is_string(except) || is_vector(except) || _is_edge_array(except))? _edges(v, except=[except]) :
4332    except==[]? _normalize_edges(sum([for (x=v) _edge_set(x)])) :
4333    _normalize_edges(
4334        _normalize_edges(sum([for (x=v) _edge_set(x)])) -
4335        sum([for (x=except) _edge_set(x)])
4336    );
4337
4338
4339/// Internal Module: _show_edges()
4340/// Topics: Edges, Debugging
4341/// Usage:
4342///   _show_edges(edges, [size=], [text=], [txtsize=]);
4343/// Description:
4344///   Draws a semi-transparent cube with the given edges highlighted in red.
4345/// Arguments:
4346///   edges = The edges to highlight.
4347///   size = The scalar size of the cube.
4348///   text = The text to show on the front of the cube.
4349///   txtsize = The size of the text.
4350/// See Also: _edges(), EDGES_NONE, EDGES_ALL
4351/// Example:
4352///   _show_edges(size=30, edges=["X","Y"]);
4353module _show_edges(edges="ALL", size=20, text, txtsize=3,toplabel) {
4354    edge_set = _edges(edges);
4355    text = !is_undef(text) ? text : _edges_text(edges);
4356    color("red") {
4357        for (axis=[0:2], i=[0:3]) {
4358            if (edge_set[axis][i] > 0) {
4359                translate(EDGE_OFFSETS[axis][i]*size/2) {
4360                    if (axis==0) xcyl(h=size, d=2);
4361                    if (axis==1) ycyl(h=size, d=2);
4362                    if (axis==2) zcyl(h=size, d=2);
4363                }
4364            }
4365        }
4366    }
4367    fwd(size/2) _edges_text3d(text, size=txtsize);
4368    color("yellow",0.7) cuboid(size=size);
4369    vpr = [55,0,25];
4370    color("black")
4371    if (is_def(toplabel))
4372      for(h=idx(toplabel)) up(21+6*h)rot(vpr) text3d(select(toplabel,-h-1),size=3.3,h=0.1,orient=UP,anchor=FRONT);
4373}
4374
4375
4376
4377
4378/// Internal Constant: CORNERS_NONE
4379/// Topics: Corners
4380/// Description:
4381///   The set of no corners.
4382/// Figure(3D):
4383///   _show_corners(corners="NONE");
4384/// See Also: CORNERS_ALL, corners()
4385CORNERS_NONE = [0,0,0,0,0,0,0,0];  // No corners.
4386
4387
4388/// Internal Constant: CORNERS_ALL
4389/// Topics: Corners
4390/// Description:
4391///   The set of all corners.
4392/// Figure(3D):
4393///   _show_corners(corners="ALL");
4394/// See Also: CORNERS_NONE, _corners()
4395CORNERS_ALL = [1,1,1,1,1,1,1,1];
4396
4397
4398/// Internal Constant: CORNER_OFFSETS
4399/// Topics: Corners
4400/// Description:
4401///   The vectors pointing to each corner of a unit sized cube.
4402///   Each item in a corner array will have a corresponding vector in this array.
4403/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4404CORNER_OFFSETS = [
4405    [-1,-1,-1], [ 1,-1,-1], [-1, 1,-1], [ 1, 1,-1],
4406    [-1,-1, 1], [ 1,-1, 1], [-1, 1, 1], [ 1, 1, 1]
4407];
4408
4409
4410
4411
4412/// Internal Function: _is_corner_array()
4413/// Topics: Corners, Type Checking
4414/// Usage:
4415///   bool = _is_corner_array(x)
4416/// Description:
4417///   Returns true if the given value has the form of a corner array.
4418/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4419function _is_corner_array(x) = is_vector(x) && len(x)==8 && all([for (xx=x) xx==1||xx==0]);
4420
4421
4422/// Internal Function: _normalize_corners()
4423/// Topics: Corners
4424/// Usage:
4425///   corns = _normalize_corners(v);
4426/// Description:
4427///   Normalizes all values in a corner array to be `1`, if it was originally greater than `0`,
4428///   or `0`, if it was originally less than or equal to `0`.
4429/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4430function _normalize_corners(v) = [for (x=v) x>0? 1 : 0];
4431
4432
4433function _corner_set(v) =
4434    _is_corner_array(v)? v : [
4435    for (i=[0:7]) let(
4436        v2 = CORNER_OFFSETS[i]
4437    ) (
4438        is_string(v)? (
4439            v=="ALL"? true :    // Return all corners.
4440            v=="NONE"? false :  // Return no corners.
4441            let(valid_values = ["ALL", "NONE"])
4442            assert(
4443                in_list(v, valid_values),
4444                str(v, " must be a vector, corner array, or one of ", valid_values)
4445            ) v
4446        ) :
4447        all([for (i=[0:2]) !v[i] || (v[i]==v2[i])])
4448    )? 1 : 0
4449];
4450
4451
4452/// Function: _corners()
4453/// Topics: Corners
4454/// Usage:
4455///   corns = _corners(v);
4456///   corns = _corners(v, except);
4457/// Description:
4458///   Takes a list of corner set descriptors, and returns a normalized corners array
4459///   that represents all those given corners.  If the `except` argument is given
4460///   a list of corner set descriptors, then all those corners will be removed
4461///   from the returned corners array.  If either argument only has a single corner
4462///   set descriptor, you do not have to pass it in a list.
4463function _corners(v, except=[]) =
4464    v==[] ? CORNERS_NONE :
4465    (is_string(v) || is_vector(v) || _is_corner_array(v))? _corners([v], except=except) :
4466    (is_string(except) || is_vector(except) || _is_corner_array(except))? _corners(v, except=[except]) :
4467    except==[]? _normalize_corners(sum([for (x=v) _corner_set(x)])) :
4468    let(
4469        a = _normalize_corners(sum([for (x=v) _corner_set(x)])),
4470        b = _normalize_corners(sum([for (x=except) _corner_set(x)]))
4471    ) _normalize_corners(a - b);
4472
4473
4474/// Internal Function: _corner_edges()
4475/// Topics: Corners
4476/// Description:
4477///   Returns [XCOUNT,YCOUNT,ZCOUNT] where each is the count of edges aligned with that
4478///   axis that are in the edge set and touch the given corner.
4479/// Arguments:
4480///   edges = Standard edges array.
4481///   v = Vector pointing to the corner to count edge intersections at.
4482/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4483function _corner_edges(edges, v) =
4484    let(u = (v+[1,1,1])/2) [edges[0][u.y+u.z*2], edges[1][u.x+u.z*2], edges[2][u.x+u.y*2]];
4485
4486
4487/// InternalFunction: _corner_edge_count()
4488/// Topics: Corners
4489/// Description:
4490///   Counts how many given edges intersect at a specific corner.
4491/// Arguments:
4492///   edges = Standard edges array.
4493///   v = Vector pointing to the corner to count edge intersections at.
4494/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4495function _corner_edge_count(edges, v) =
4496    let(u = (v+[1,1,1])/2) edges[0][u.y+u.z*2] + edges[1][u.x+u.z*2] + edges[2][u.x+u.y*2];
4497
4498
4499function _corners_text(corners) =
4500    is_string(corners) ? [str("\"",corners,"\"")] :
4501    corners==CORNERS_NONE ? ["CORNERS_NONE"] :
4502    corners==CORNERS_ALL ? ["CORNERS_ALL"] :
4503    _is_corner_array(corners) ? [""] :
4504    is_vector(corners,3) ? _edges_vec_txt(corners) :
4505    is_list(corners) ? let(
4506        lst = [for (x=corners) each _corners_text(x)],
4507        out = [
4508            for (i=idx(lst))
4509            str(
4510                (i==0? "[" : ""),
4511                lst[i],
4512                (i<len(lst)-1? "," : ""),
4513                (i==len(lst)-1? "]" : "")
4514            )
4515        ]
4516    ) out :
4517    [""];
4518
4519
4520/// Internal Module: _show_corners()
4521/// Topics: Corners, Debugging
4522/// Usage:
4523///   _show_corners(corners, [size=], [text=], [txtsize=]);
4524/// Description:
4525///   Draws a semi-transparent cube with the given corners highlighted in red.
4526/// Arguments:
4527///   corners = The corners to highlight.
4528///   size = The scalar size of the cube.
4529///   text = If given, overrides the text to be shown on the front of the cube.
4530///   txtsize = The size of the text.
4531/// See Also: CORNERS_NONE, CORNERS_ALL, corners()
4532/// Example:
4533///   _show_corners(corners=FWD+RIGHT, size=30);
4534module _show_corners(corners="ALL", size=20, text, txtsize=3,toplabel) {
4535    corner_set = _corners(corners);
4536    text = !is_undef(text) ? text : _corners_text(corners);
4537    for (i=[0:7]) if (corner_set[i]>0)
4538        translate(CORNER_OFFSETS[i]*size/2)
4539            color("red") sphere(d=2, $fn=16);
4540    fwd(size/2) _edges_text3d(text, size=txtsize);
4541    color("yellow",0.7) cuboid(size=size);
4542    vpr = [55,0,25];
4543    color("black")
4544    if (is_def(toplabel))
4545      for(h=idx(toplabel)) up(21+6*h)rot(vpr) text3d(select(toplabel,-h-1),size=3.3,h=.1,orient=UP,anchor=FRONT);
4546}
4547
4548module _show_cube_faces(faces, size=20, toplabel,botlabel) {
4549   color("red")
4550     for(f=faces){
4551          move(f*size/2) rot(from=UP,to=f)
4552             cuboid([size,size,.1]);
4553     }
4554   vpr = [55,0,25];
4555   color("black"){
4556   if (is_def(toplabel))
4557     for(h=idx(toplabel)) up(21+6*h)rot(vpr) text3d(select(toplabel,-h-1),size=3.3,h=.1,orient=UP,anchor=FRONT);
4558   if (is_def(botlabel))
4559     for(h=idx(botlabel)) down(26+6*h)rot(vpr) text3d(botlabel[h],size=3.3,h=.1,orient=UP,anchor=FRONT);
4560   }
4561   color("yellow",0.7) cuboid(size=size);
4562}
4563
4564// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap